Skip to content

Commit

Permalink
Added HMAC authentication mode in Hash generators (#555)
Browse files Browse the repository at this point in the history
* Added first prototype of hmac feature

Todo: localized text and an icon for the HMAC mode toggle switch and secret key text box label

* Added an icon for HMAC

* Fixed compilation errors after merging from main

* Added unit tests, globalized strings
  • Loading branch information
L1nu5 authored Jun 6, 2022
1 parent 4854f9a commit 389ba74
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 15 deletions.
10 changes: 10 additions & 0 deletions src/dev/impl/DevToys/LanguageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,16 @@ public class HashGeneratorStrings : ObservableObject
/// Gets the resource SearchKeywords.
/// </summary>
public string SearchKeywords => _resources.GetString("SearchKeywords");

/// <summary>
/// Gets the resource HmacMode.
/// </summary>
public string HmacMode => _resources.GetString("HmacMode");

/// <summary>
/// Gets the resource SecretKey.
/// </summary>
public string SecretKey => _resources.GetString("SecretKey");
}

public class HtmlEncoderDecoderStrings : ObservableObject
Expand Down
6 changes: 6 additions & 0 deletions src/dev/impl/DevToys/Strings/en-US/HashGenerator.resw
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,10 @@
<data name="SearchKeywords" xml:space="preserve">
<value />
</data>
<data name="HmacMode" xml:space="preserve">
<value>HMAC Mode</value>
</data>
<data name="SecretKey" xml:space="preserve">
<value>Secret Key</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Composition;
using System.Threading.Tasks;
using DevToys.Api.Core;
Expand All @@ -17,7 +18,7 @@
namespace DevToys.ViewModels.Tools.HashGenerator
{
[Export(typeof(HashGeneratorToolViewModel))]
public sealed class HashGeneratorToolViewModel : QueueWorkerViewModelBase<string>, IToolViewModel
public sealed class HashGeneratorToolViewModel : QueueWorkerViewModelBase<Tuple<string,string>>, IToolViewModel
{
/// <summary>
/// Whether the generated hash should be uppercase or lowercase.
Expand All @@ -28,6 +29,15 @@ private static readonly SettingDefinition<bool> Uppercase
isRoaming: true,
defaultValue: false);

/// <summary>
/// Whether the tool should operate and return HMAC string
/// </summary>
private static readonly SettingDefinition<bool> IsHMAC
= new(
name: $"{nameof(HashGeneratorToolViewModel)}.{nameof(IsHMAC)}",
isRoaming: true,
defaultValue: false);

/// <summary>
/// The Output Code to generate.
/// </summary>
Expand All @@ -43,6 +53,7 @@ private static readonly SettingDefinition<string> OutType

private bool _toolSuccessfullyWorked;
private string? _input;
private string? _secretKey;
private string? _md5;
private string? _sha1;
private string? _sha256;
Expand Down Expand Up @@ -83,6 +94,20 @@ internal string OutputType
}
}

internal bool IsHmacMode
{
get => SettingsProvider.GetSetting(IsHMAC);
set
{
if(SettingsProvider.GetSetting(IsHMAC) != value)
{
SettingsProvider.SetSetting(IsHMAC, value);
OnPropertyChanged();
QueueHashCalculation();
}
}
}

internal string? Input
{
get => _input;
Expand All @@ -93,6 +118,16 @@ internal string? Input
}
}

internal string? SecretKey
{
get => _secretKey;
set
{
SetProperty(ref _secretKey, value);
QueueHashCalculation();
}
}

internal string? MD5
{
get => _md5;
Expand Down Expand Up @@ -126,15 +161,15 @@ public HashGeneratorToolViewModel(ISettingsProvider settingsProvider, IMarketing

private void QueueHashCalculation()
{
EnqueueComputation(Input ?? string.Empty);
EnqueueComputation(new Tuple<string, string>(Input ?? string.Empty, SecretKey ?? string.Empty));
}

protected override async Task TreatComputationQueueAsync(string value)
protected override async Task TreatComputationQueueAsync(Tuple<string, string> inputSecretKeyPair)
{
Task<string> md5CalculationTask = CalculateHashAsync(HashAlgorithmNames.Md5, value);
Task<string> sha1CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha1, value);
Task<string> sha256CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha256, value);
Task<string> sha512CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha512, value);
Task<string> md5CalculationTask = CalculateHashAsync(HashAlgorithmNames.Md5, inputSecretKeyPair.Item1, inputSecretKeyPair.Item2);
Task<string> sha1CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha1, inputSecretKeyPair.Item1, inputSecretKeyPair.Item2);
Task<string> sha256CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha256, inputSecretKeyPair.Item1, inputSecretKeyPair.Item2);
Task<string> sha512CalculationTask = CalculateHashAsync(HashAlgorithmNames.Sha512, inputSecretKeyPair.Item1, inputSecretKeyPair.Item2);

await Task.WhenAll(md5CalculationTask).ConfigureAwait(false);

Expand All @@ -153,9 +188,9 @@ await ThreadHelper.RunOnUIThreadAsync(() =>
});
}

private async Task<string> CalculateHashAsync(string alrogithmName, string text)
private async Task<string> CalculateHashAsync(string algorithmName, string text, string secretKey)
{
if (string.IsNullOrEmpty(text))
if (string.IsNullOrEmpty(text) || (IsHmacMode && secretKey.Length == 0))
{
return string.Empty;
}
Expand All @@ -164,12 +199,23 @@ private async Task<string> CalculateHashAsync(string alrogithmName, string text)

try
{
var algorithmProvider = HashAlgorithmProvider.OpenAlgorithm(alrogithmName);

IBuffer buffer = CryptographicBuffer.ConvertStringToBinary(text, BinaryStringEncoding.Utf8);
buffer = algorithmProvider.HashData(buffer);

string? hash = "";
IBuffer? buffer = null;
if (IsHmacMode)
{
var macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(GetHmacAlgorithmName(algorithmName));
IBuffer textBuffer = CryptographicBuffer.ConvertStringToBinary(text, BinaryStringEncoding.Utf8);
IBuffer secretKeyBuffer = CryptographicBuffer.ConvertStringToBinary(secretKey, BinaryStringEncoding.Utf8);
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(secretKeyBuffer);
buffer = CryptographicEngine.Sign(hmacKey, textBuffer);
}
else
{
var algorithmProvider = HashAlgorithmProvider.OpenAlgorithm(algorithmName);
buffer = CryptographicBuffer.ConvertStringToBinary(text, BinaryStringEncoding.Utf8);
buffer = algorithmProvider.HashData(buffer);
}

if (string.Equals(OutputType, HexOutput))
{
hash = IsUppercase
Expand All @@ -184,9 +230,31 @@ private async Task<string> CalculateHashAsync(string alrogithmName, string text)
}
catch (Exception ex)
{
Logger.LogFault("Hash Generator", ex, $"Alrogithm name: {alrogithmName}");
Logger.LogFault("Hash Generator", ex, $"Alrogithm name: {algorithmName}");
return ex.Message;
}
}

private string GetHmacAlgorithmName(string algorithmName)
{
if (algorithmName == HashAlgorithmNames.Md5)
{
return MacAlgorithmNames.HmacMd5;
}
else if (algorithmName == HashAlgorithmNames.Sha1)
{
return MacAlgorithmNames.HmacSha1;
}
else if (algorithmName == HashAlgorithmNames.Sha256)
{
return MacAlgorithmNames.HmacSha256;
}
else if (algorithmName == HashAlgorithmNames.Sha512)
{
return MacAlgorithmNames.HmacSha512;
}

throw new Exception("Unsupported algorithm: " + algorithmName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@
<ComboBoxItem Tag="Base64" Content="{x:Bind ViewModel.Strings.OutputBase64}"/>
</ComboBox>
</controls:ExpandableSettingControl>
<controls:ExpandableSettingControl
Title="{x:Bind ViewModel.Strings.HmacMode}">
<controls:ExpandableSettingControl.Icon>
<FontIcon Glyph="&#62734;" />
</controls:ExpandableSettingControl.Icon>
<ToggleSwitch
x:Name="IsUsingHmacToggleSwitch"
Style="{StaticResource RightAlignedToggleSwitchStyle}"
Toggled="IsUsingHmacToggleSwitch_Toggled"
IsOn="{x:Bind ViewModel.IsHmacMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</controls:ExpandableSettingControl>
</StackPanel>

<controls:CustomTextBox
Expand All @@ -50,6 +61,13 @@
AcceptsReturn="True"
Text="{x:Bind ViewModel.Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

<controls:CustomTextBox x:Name="SecretKeyInput"
Header="{x:Bind ViewModel.Strings.SecretKey}"
Height="150"
AcceptsReturn="True"
Visibility="Collapsed"
Text="{x:Bind ViewModel.SecretKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

<controls:CustomTextBox
Header="{x:Bind ViewModel.Strings.MD5}"
IsReadOnly="True"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,23 @@ private void OutputType_SelectionChanged(object sender, SelectionChangedEventArg
IsUppercaseToggleSwitch.IsEnabled = true;
}
}

private void IsUsingHmacToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
var toggleSwitch = (ToggleSwitch)sender;
if(toggleSwitch != null)
{
ViewModel.IsHmacMode = toggleSwitch.IsOn;
if (toggleSwitch.IsOn)
{
SecretKeyInput.Visibility = Visibility.Visible;
}
else
{
SecretKeyInput.Visibility = Visibility.Collapsed;
SecretKeyInput.Text = "";
}
}
}
}
}
44 changes: 44 additions & 0 deletions src/tests/DevToys.Tests/Providers/Tools/HashGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,49 @@ await ThreadHelper.RunOnUIThreadAsync(() =>

Assert.AreEqual(expectedResult, viewModel.SHA512);
}

[DataTestMethod]
[DataRow(null, "", "")]
[DataRow("", "", "")]
[DataRow(" ", " ", "0b8a72163b925bbb61ffa98e90339e57f0ed5c8956665af83691aebbdebb87e7eb6090a877b62fdcfca2e29768159d0066e7ef875a87d6a8b2ff9d286a98ff56")]
[DataRow("Hello There", "World", "43bb6d1170cbf1be61ebf85a434ea1acb4caced09b8f0d40125b21804c524a4be66ba0af02617076505973edf819563e4d8eb68a5c2c4dc5d1a0a661bcce5d44")]
public async Task LowercaseHmacModeHashingAsync(string input, string secretKey, string expectedResult)
{
HashGeneratorToolViewModel viewModel = ExportProvider.Import<HashGeneratorToolViewModel>();

await ThreadHelper.RunOnUIThreadAsync(() =>
{
viewModel.IsUppercase = false;
viewModel.Input = input;
viewModel.IsHmacMode = true;
viewModel.SecretKey = secretKey;
});

await viewModel.ComputationTask;

Assert.AreEqual(expectedResult, viewModel.SHA512);
}

[DataTestMethod]
[DataRow(null, "", "")]
[DataRow("", "", "")]
[DataRow(" ", " ", "0B8A72163B925BBB61FFA98E90339E57F0ED5C8956665AF83691AEBBDEBB87E7EB6090A877B62FDCFCA2E29768159D0066E7EF875A87D6A8B2FF9D286A98FF56")]
[DataRow("Hello There", "World", "43BB6D1170CBF1BE61EBF85A434EA1ACB4CACED09B8F0D40125B21804C524A4BE66BA0AF02617076505973EDF819563E4D8EB68A5C2C4DC5D1A0A661BCCE5D44")]
public async Task UppercaseHmacModeHashingAsync(string input, string secretKey, string expectedResult)
{
HashGeneratorToolViewModel viewModel = ExportProvider.Import<HashGeneratorToolViewModel>();

await ThreadHelper.RunOnUIThreadAsync(() =>
{
viewModel.IsUppercase = true;
viewModel.Input = input;
viewModel.IsHmacMode = true;
viewModel.SecretKey = secretKey;
});

await viewModel.ComputationTask;

Assert.AreEqual(expectedResult, viewModel.SHA512);
}
}
}

0 comments on commit 389ba74

Please sign in to comment.