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:
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 "secret" 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=""dependent" (faster but susceptible to side-channel attacks), "independent" (slower and suitable for password hashing and password-based key derivation), or "hybrid" (a mixture of the two). Defaults to the recommended type: "hybrid"." 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