Show / Hide Table of Contents

Argon2 With UnoPlatform WebAssembly

You can use this library to calculate Argon2 hashes in the web browser. With the .Net 6.0 SDK, the Blazor WebAssembly runs slower than on the host - taking on the order of 4-8 times longer for a default hash on common hardware. I couldn't get the Uno Platform example to compile with the Project->PropertyGroup->WasmShellMonoRuntimeExecutionMode setting to anything other than Interpreter (InterpreterAndAOT and FullAOT failed to build). This should improve as both dotnet improves and WebAssembly improves.

Note, unlike the current Blazor, the Uno Plaform says it supports .Net threads (see Threads Support. Please tell me how if you get it to work - I have failed with the page hanging using 1.3.4 and failed to even load the page properly when using 1.4.0-dev.52.

UWP

One of UnoPlatform's output types is for a Universal Windows Application (UWP). I have yet to figure out out to call VirtualAllocFromApp() which should be available for UWP apps and should allow protection from writing to cache for SecureArray. So, for now, only zero-before-free is available with UWP.

Example

This example tries to be a little friendly in that it tells you when it is calculating the hash and disables the controls when it is doing so.

It builds a component that looks like this:

HashComponent

XAML

The WPF XAML for that page looks like:

<Page
    x:Class="TestUno.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TestUno"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"    
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    xmlns:numeric="http://gu.se/NumericInput"
    d:DesignWidth="1000">

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Name="HashTitle" Text="" Margin="5" FontSize="25" />
        <TextBlock Name="Os" Text="" Margin="5" FontSize="25" />
        <TextBlock Name="HashValue" Text="" Margin="5" FontSize="25" />
        <TextBlock Name="HashTime" Text="" Margin= "5" FontSize="25" />
        <StackPanel Margin="0,20,0,20">
            <StackPanel Orientation="Horizontal" Padding="4" Spacing="10">
                <TextBox x:Name="Secret" Text="" Width="200" FontSize="25"/>
                <TextBlock Text="The &quot;secret&quot; to hash" FontSize="25" />
            </StackPanel>
            <TextBlock Text="Hashing occurs when leaving the secret input field." Padding="4" FontSize="25" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Padding="4" Spacing="10">
            <TextBox x:Name="TimeCost" Text="3" Width="200" FontSize="25"/>
            <TextBlock Text="Time cost. Defaults to 3." FontSize="25" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Padding="4" Spacing="10">
            <TextBox x:Name="MemoryCost" Text="65536" Width="200" FontSize="25" />
            <TextBlock Text="Memory cost. Defaults to 65536 (65536 * 1024 = 64MB)." FontSize="25" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Padding="4" Spacing="10">
            <TextBox Name="Parallelism" Text="1" Width="200" FontSize="25" />
            <TextBlock Text="Parallelism. Defaults to 1." FontSize="25" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Padding="4" Spacing="10" >
            <ComboBox x:Name="Type" Width="200" SelectedItem="hybrid" FontSize="25">
                <ComboBoxItem IsSelected="False">dependent</ComboBoxItem>
                <ComboBoxItem IsSelected="False">independent</ComboBoxItem>
                <ComboBoxItem IsSelected="True">hybrid</ComboBoxItem>
            </ComboBox>
            <TextBlock Text="&quot;dependent&quot; (faster but susceptible to side-channel attacks), &quot;independent&quot; (slower and suitable for password hashing and password-based key derivation), or &quot;hybrid&quot; (a mixture of the two). Defaults to the recommended type: &quot;hybrid&quot;." TextWrapping="WrapWholeWords" MaxWidth="700"  FontSize="25" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Padding="4" Spacing="10">
            <TextBox x:Name="HashLength" Text="32" Width="200" FontSize="25" />
            <TextBlock Text="Hash length. The hash string base-64 encodes the hash of this length along with other parameters so the length of the resulting hash string is significantly longer." TextWrapping="WrapWholeWords" MaxWidth="700" FontSize="25" />
        </StackPanel>
    </StackPanel>
</Page>

XAML Code

The code to do the processing for that XAML takes care to do as much proccessing in async calls to give the UI a chance to be reactive. Highlighted is the actual call to Argon2.Hash():

// <copyright file="MainPage.xaml.cs" company="Isopoh">
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// </copyright>
namespace TestUno
{
    using System;
    using System.Threading.Tasks;
    using Isopoh.Cryptography.Argon2;
    using Isopoh.Cryptography.SecureArray;
    using Windows.UI.Xaml.Controls;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private string previousSecret = string.Empty;

        /// <summary>
        /// Initializes a new instance of the <see cref="MainPage"/> class.
        /// </summary>
        public MainPage()
        {
            this.InitializeComponent();
            this.Os.Text = $"Operating System: {SecureArray.DefaultCall.Os}, {IntPtr.Size * 8}-bit";
            this.Secret.LostFocus += (o, a) => Task.Run(
                async () => await this.CalculateHashAsync());
            this.TimeCost.BeforeTextChanging += this.OnBeforePositiveIntTextChange;
            this.MemoryCost.BeforeTextChanging += this.OnBeforePositiveIntTextChange;
            this.Parallelism.BeforeTextChanging += this.OnBeforePositiveIntTextChange;
            this.HashLength.BeforeTextChanging += this.OnBeforePositiveIntTextChange;
        }

        /// <summary>
        /// Called before a positive integer text change.
        /// </summary>
        /// <param name="o">The object called on.</param>
        /// <param name="arg">The text change event information.</param>
        public void OnBeforePositiveIntTextChange(
            TextBox o,
            TextBoxBeforeTextChangingEventArgs arg)
        {
            arg.Cancel = !int.TryParse(arg.NewText, out int val) || val < 1;
        }

        /// <summary>
        /// Called to calculate the hash with the parameters from the form.
        /// </summary>
        /// <returns>Task that calculates the hash.</returns>
        public async Task CalculateHashAsync()
        {
            bool textChanged = false;
            await this.Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal,
                () =>
                {
                    textChanged = this.Secret.Text != this.previousSecret;
                });
            if (textChanged)
            {
                var tick = DateTimeOffset.UtcNow;
                await this.Dispatcher.RunAsync(
                    Windows.UI.Core.CoreDispatcherPriority.Normal,
                    () =>
                    {
                        this.previousSecret = this.Secret.Text;
                        this.Secret.IsEnabled = false;
                        this.TimeCost.IsEnabled = false;
                        this.MemoryCost.IsEnabled = false;
                        this.Parallelism.IsEnabled = false;
                        this.Type.IsEnabled = false;
                        this.HashLength.IsEnabled = false;
                        this.HashTitle.Text = string.Empty;
                        this.HashValue.Text =
                            $"Calculating hash for \"{this.previousSecret}\"...";
                        this.HashTime.Text = string.Empty;
                    });
                try
                {
                    int timeCost = 3;
                    int memoryCost = 65536;
                    int parallelism = 1;
                    Argon2Type type = Argon2Type.HybridAddressing;
                    int hashLength = 32;
                    await this.Dispatcher.RunAsync(
                        Windows.UI.Core.CoreDispatcherPriority.Normal,
                        () =>
                        {
                            if (!int.TryParse(this.TimeCost.Text, out timeCost)
                                || timeCost < 1)
                            {
                                timeCost = 3;
                                this.TimeCost.Text = "3";
                            }

                            if (!int.TryParse(this.MemoryCost.Text, out memoryCost)
                                || memoryCost < 1)
                            {
                                memoryCost = 65536;
                                this.MemoryCost.Text = "65536";
                            }

                            if (!int.TryParse(this.Parallelism.Text, out parallelism)
                                || parallelism < 1)
                            {
                                parallelism = 1;
                                this.Parallelism.Text = "1";
                            }

                            if (this.Type.SelectedIndex == 0)
                            {
                                type = Argon2Type.DataDependentAddressing;
                            }
                            else if (this.Type.SelectedIndex == 1)
                            {
                                type = Argon2Type.DataIndependentAddressing;
                            }
                            else
                            {
                                type = Argon2Type.HybridAddressing;
                                this.Type.SelectedIndex = 2;
                            }

                            if (!int.TryParse(this.HashLength.Text, out hashLength)
                                || hashLength < 1)
                            {
                                hashLength = 32;
                                this.HashLength.Text = "32";
                            }
                        });

                    var hashValue = await Task.Run(
                        () => Argon2.Hash(
                            this.previousSecret,
                            timeCost,
                            memoryCost,
                            parallelism,
                            type,
                            hashLength));
                    var hashTime =
                        ((int)(DateTimeOffset.UtcNow - tick).TotalMilliseconds) / 1000.0;
                    var hashTimeText = $"({hashTime} seconds)";
                    await this.Dispatcher.RunAsync(
                        Windows.UI.Core.CoreDispatcherPriority.Normal,
                        () =>
                        {
                            this.HashTitle.Text = $"Hash for \"{this.previousSecret}\".";
                            this.HashValue.Text = hashValue;
                            this.HashTime.Text = hashTimeText;
                        });
                }
                finally
                {
                    await this.Dispatcher.RunAsync(
                        Windows.UI.Core.CoreDispatcherPriority.Normal,
                        () =>
                        {
                            this.Secret.IsEnabled = true;
                            this.TimeCost.IsEnabled = true;
                            this.MemoryCost.IsEnabled = true;
                            this.Parallelism.IsEnabled = true;
                            this.Type.IsEnabled = true;
                            this.HashLength.IsEnabled = true;
                        });
                }
            }
        }
    }
}

Example Source

The source for this example can be found at:

(github)TestUno

  • Improve this Doc
In This Article
Back to top Generated by DocFX