Skip to content

Commit

Permalink
Merge pull request #198 from BrandonPotter/issue/197_HMAC256
Browse files Browse the repository at this point in the history
Added support for HMACSHA 256 and 512
  • Loading branch information
ahwm authored Jan 11, 2024
2 parents f713b7e + e49f0ad commit 585a5b1
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 64 deletions.
68 changes: 68 additions & 0 deletions Google.Authenticator.Tests/AuthCodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,40 @@ public void BasicAuthCodeTest()
actual.ShouldBe(expected);
}

[Fact]
public void BasicAuthCodeTestSHA256()
{
var secretKey = "PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH";
var expected = "002939";

var tfa = new TwoFactorAuthenticator(HashType.SHA256);

var currentTime = 1416643820;

// I actually think you are supposed to divide the time by 30 seconds?
// Maybe need an overload that takes a DateTime?
var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6);

actual.ShouldBe(expected);
}

[Fact]
public void BasicAuthCodeTestSHA512()
{
var secretKey = "PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH";
var expected = "461461";

var tfa = new TwoFactorAuthenticator(HashType.SHA512);

var currentTime = 1416643820;

// I actually think you are supposed to divide the time by 30 seconds?
// Maybe need an overload that takes a DateTime?
var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6);

actual.ShouldBe(expected);
}

[Fact]
public void Base32AuthCodeTest()
{
Expand All @@ -39,5 +73,39 @@ public void Base32AuthCodeTest()

actual.ShouldBe(expected);
}

[Fact]
public void Base32AuthCodeTestSHA256()
{
var secretKey = Base32Encoding.ToString(Encoding.UTF8.GetBytes("PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH"));
var expected = "002939";

var tfa = new TwoFactorAuthenticator(HashType.SHA256);

var currentTime = 1416643820;

// I actually think you are supposed to divide the time by 30 seconds?
// Maybe need an overload that takes a DateTime?
var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6, true);

actual.ShouldBe(expected);
}

[Fact]
public void Base32AuthCodeTestSHA512()
{
var secretKey = Base32Encoding.ToString(Encoding.UTF8.GetBytes("PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH"));
var expected = "461461";

var tfa = new TwoFactorAuthenticator(HashType.SHA512);

var currentTime = 1416643820;

// I actually think you are supposed to divide the time by 30 seconds?
// Maybe need an overload that takes a DateTime?
var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6, true);

actual.ShouldBe(expected);
}
}
}
38 changes: 38 additions & 0 deletions Google.Authenticator.Tests/GeneratePinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,44 @@ public void OverloadsReturnSamePIN()
var pinFromBytes = subject.GeneratePINAtInterval(secretAsBytes, counter);
var pinFromBase32 = subject.GeneratePINAtInterval(secretAsBase32, counter, secretIsBase32: true);

pinFromString.ShouldBe(expected);
pinFromBytes.ShouldBe(expected);
pinFromBase32.ShouldBe(expected);
}
[Fact]
public void OverloadsReturnSamePINSHA256()
{
var secret = "JBSWY3DPEHPK3PXP";
var secretAsBytes = Encoding.UTF8.GetBytes(secret);
var secretAsBase32 = Base32Encoding.ToString(secretAsBytes);
long counter = 54615912;
var expected = "087141";

var subject = new TwoFactorAuthenticator(HashType.SHA256);

var pinFromString = subject.GeneratePINAtInterval(secret, counter);
var pinFromBytes = subject.GeneratePINAtInterval(secretAsBytes, counter);
var pinFromBase32 = subject.GeneratePINAtInterval(secretAsBase32, counter, secretIsBase32: true);

pinFromString.ShouldBe(expected);
pinFromBytes.ShouldBe(expected);
pinFromBase32.ShouldBe(expected);
}
[Fact]
public void OverloadsReturnSamePINSHA512()
{
var secret = "JBSWY3DPEHPK3PXP";
var secretAsBytes = Encoding.UTF8.GetBytes(secret);
var secretAsBase32 = Base32Encoding.ToString(secretAsBytes);
long counter = 54615912;
var expected = "397230";

var subject = new TwoFactorAuthenticator(HashType.SHA512);

var pinFromString = subject.GeneratePINAtInterval(secret, counter);
var pinFromBytes = subject.GeneratePINAtInterval(secretAsBytes, counter);
var pinFromBase32 = subject.GeneratePINAtInterval(secretAsBase32, counter, secretIsBase32: true);

pinFromString.ShouldBe(expected);
pinFromBytes.ShouldBe(expected);
pinFromBase32.ShouldBe(expected);
Expand Down
38 changes: 38 additions & 0 deletions Google.Authenticator.Tests/QRCodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,44 @@ public void CanGenerateQRCode(string issuer, string expectedUrl)
actualUrl.ShouldBe(expectedUrl);
}

[Theory]
[InlineData("issuer", "otpauth://totp/issuer:a%40b.com?secret=ONSWG4TFOQ&issuer=issuer&algorithm=SHA256")]
[InlineData("Foo & Bar", "otpauth://totp/Foo%20%26%20Bar:a%40b.com?secret=ONSWG4TFOQ&issuer=Foo%20%26%20Bar&algorithm=SHA256")]
[InlineData("个", "otpauth://totp/%E4%B8%AA:a%40b.com?secret=ONSWG4TFOQ&issuer=%E4%B8%AA&algorithm=SHA256")]
public void CanGenerateSHA256QRCode(string issuer, string expectedUrl)
{
var subject = new TwoFactorAuthenticator(HashType.SHA256);
var setupCodeInfo = subject.GenerateSetupCode(
issuer,
"a@b.com",
"secret",
false,
2);

var actualUrl = ExtractUrlFromQRImage(setupCodeInfo.QrCodeSetupImageUrl);

actualUrl.ShouldBe(expectedUrl);
}

[Theory]
[InlineData("issuer", "otpauth://totp/issuer:a%40b.com?secret=ONSWG4TFOQ&issuer=issuer&algorithm=SHA512")]
[InlineData("Foo & Bar", "otpauth://totp/Foo%20%26%20Bar:a%40b.com?secret=ONSWG4TFOQ&issuer=Foo%20%26%20Bar&algorithm=SHA512")]
[InlineData("个", "otpauth://totp/%E4%B8%AA:a%40b.com?secret=ONSWG4TFOQ&issuer=%E4%B8%AA&algorithm=SHA512")]
public void CanGenerateSHA512QRCode(string issuer, string expectedUrl)
{
var subject = new TwoFactorAuthenticator(HashType.SHA512);
var setupCodeInfo = subject.GenerateSetupCode(
issuer,
"a@b.com",
"secret",
false,
2);

var actualUrl = ExtractUrlFromQRImage(setupCodeInfo.QrCodeSetupImageUrl);

actualUrl.ShouldBe(expectedUrl);
}

private static string ExtractUrlFromQRImage(string qrCodeSetupImageUrl)
{
var headerLength = "data:image/png;base64,".Length;
Expand Down
42 changes: 42 additions & 0 deletions Google.Authenticator.Tests/SetupCodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,47 @@ public void ByteAndStringGeneratesSameSetupCode()
setupCodeFromByteArray.ManualEntryKey.ShouldBe(expected);
setupCodeFromBase32.ManualEntryKey.ShouldBe(expected);
}

[Fact]
public void ByteAndStringGeneratesSameSetupCodeSHA256()
{
var secret = "12345678901234567890123456789012";
var secretAsByteArray = Encoding.UTF8.GetBytes(secret);
var secretAsBase32 = Base32Encoding.ToString(secretAsByteArray);
var issuer = "Test";
var accountName = "TestAccount";
var expected = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA";

var subject = new TwoFactorAuthenticator(HashType.SHA256);

var setupCodeFromString = subject.GenerateSetupCode(issuer, accountName, secret, false);
var setupCodeFromByteArray = subject.GenerateSetupCode(issuer, accountName, secretAsByteArray, 3, false);
var setupCodeFromBase32 = subject.GenerateSetupCode(issuer, accountName, secretAsBase32, true);

setupCodeFromString.ManualEntryKey.ShouldBe(expected);
setupCodeFromByteArray.ManualEntryKey.ShouldBe(expected);
setupCodeFromBase32.ManualEntryKey.ShouldBe(expected);
}

[Fact]
public void ByteAndStringGeneratesSameSetupCodeSHA512()
{
var secret = "12345678901234567890123456789012";
var secretAsByteArray = Encoding.UTF8.GetBytes(secret);
var secretAsBase32 = Base32Encoding.ToString(secretAsByteArray);
var issuer = "Test";
var accountName = "TestAccount";
var expected = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA";

var subject = new TwoFactorAuthenticator(HashType.SHA512);

var setupCodeFromString = subject.GenerateSetupCode(issuer, accountName, secret, false);
var setupCodeFromByteArray = subject.GenerateSetupCode(issuer, accountName, secretAsByteArray, 3, false);
var setupCodeFromBase32 = subject.GenerateSetupCode(issuer, accountName, secretAsBase32, true);

setupCodeFromString.ManualEntryKey.ShouldBe(expected);
setupCodeFromByteArray.ManualEntryKey.ShouldBe(expected);
setupCodeFromBase32.ManualEntryKey.ShouldBe(expected);
}
}
}
102 changes: 101 additions & 1 deletion Google.Authenticator.Tests/ValidationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Xunit;
using Shouldly;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System;
Expand Down Expand Up @@ -33,6 +32,46 @@ public void ValidateWorksWithDifferentSecretTypes(string pin, int irrelevantNumb
subject.ValidateTwoFactorPIN(secretAsBytes, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2);
}

[Theory]
[MemberData(nameof(GetPins), HashType.SHA256)]
public void ValidateWorksWithDifferentSecretTypesSHA256(string pin, int irrelevantNumberToAvoidDuplicatePinsBeingRemoved)
{
// We can't directly test that the different overloads for GetCurrentPIN creates the same result,
// as the time difference may may cause different PINS to be created.
// So instead we generate the PINs by each method and validate each one by each method.
var subject = new TwoFactorAuthenticator(HashType.SHA256);

subject.ValidateTwoFactorPIN(secret, pin, false);
subject.ValidateTwoFactorPIN(secret, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), false);
subject.ValidateTwoFactorPIN(secretAsBytes, pin);
subject.ValidateTwoFactorPIN(secretAsBytes, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved));
subject.ValidateTwoFactorPIN(secretAsBase32, pin, true);
subject.ValidateTwoFactorPIN(secretAsBase32, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), true);
subject.ValidateTwoFactorPIN(secret, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2, false);
subject.ValidateTwoFactorPIN(secretAsBase32, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2, true);
subject.ValidateTwoFactorPIN(secretAsBytes, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2);
}

[Theory]
[MemberData(nameof(GetPins), HashType.SHA512)]
public void ValidateWorksWithDifferentSecretTypesSHA512(string pin, int irrelevantNumberToAvoidDuplicatePinsBeingRemoved)
{
// We can't directly test that the different overloads for GetCurrentPIN creates the same result,
// as the time difference may may cause different PINS to be created.
// So instead we generate the PINs by each method and validate each one by each method.
var subject = new TwoFactorAuthenticator(HashType.SHA512);

subject.ValidateTwoFactorPIN(secret, pin, false);
subject.ValidateTwoFactorPIN(secret, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), false);
subject.ValidateTwoFactorPIN(secretAsBytes, pin);
subject.ValidateTwoFactorPIN(secretAsBytes, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved));
subject.ValidateTwoFactorPIN(secretAsBase32, pin, true);
subject.ValidateTwoFactorPIN(secretAsBase32, pin, TimeSpan.FromMinutes(irrelevantNumberToAvoidDuplicatePinsBeingRemoved), true);
subject.ValidateTwoFactorPIN(secret, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2, false);
subject.ValidateTwoFactorPIN(secretAsBase32, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2, true);
subject.ValidateTwoFactorPIN(secretAsBytes, pin, irrelevantNumberToAvoidDuplicatePinsBeingRemoved * 2);
}

[Fact]
public void GetCurrentPinsHandles15SecondInterval()
{
Expand All @@ -42,6 +81,23 @@ public void GetCurrentPinsHandles15SecondInterval()
subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(15)).Length.ShouldBe(1);
}

[Fact]
public void GetCurrentPinsHandles15SecondIntervalSHA256()
{
// This is nonsensical, really, as anything less than 30 == 0 in practice.
var subject = new TwoFactorAuthenticator(HashType.SHA256);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(15)).Length.ShouldBe(1);
}

[Fact]
public void GetCurrentPinsHandles15SecondIntervalSHA512()
{
// This is nonsensical, really, as anything less than 30 == 0 in practice.
var subject = new TwoFactorAuthenticator(HashType.SHA512);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(15)).Length.ShouldBe(1);
}

[Fact]
public void GetCurrentPinsHandles30SecondInterval()
Expand All @@ -51,6 +107,22 @@ public void GetCurrentPinsHandles30SecondInterval()
subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(30)).Length.ShouldBe(3);
}

[Fact]
public void GetCurrentPinsHandles30SecondIntervalSHA256()
{
var subject = new TwoFactorAuthenticator(HashType.SHA256);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(30)).Length.ShouldBe(3);
}

[Fact]
public void GetCurrentPinsHandles30SecondIntervalSHA512()
{
var subject = new TwoFactorAuthenticator(HashType.SHA512);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(30)).Length.ShouldBe(3);
}

[Fact]
public void GetCurrentPinsHandles60SecondInterval()
{
Expand All @@ -59,6 +131,22 @@ public void GetCurrentPinsHandles60SecondInterval()
subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(60)).Length.ShouldBe(5);
}

[Fact]
public void GetCurrentPinsHandles60SecondIntervalSHA256()
{
var subject = new TwoFactorAuthenticator(HashType.SHA256);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(60)).Length.ShouldBe(5);
}

[Fact]
public void GetCurrentPinsHandles60SecondIntervalSHA512()
{
var subject = new TwoFactorAuthenticator(HashType.SHA512);

subject.GetCurrentPINs(secret, TimeSpan.FromSeconds(60)).Length.ShouldBe(5);
}

public static IEnumerable<object[]> GetPins()
{
var subject = new TwoFactorAuthenticator();
Expand All @@ -70,5 +158,17 @@ public static IEnumerable<object[]> GetPins()
yield return new object[] { subject.GetCurrentPIN(secretAsBase32, true), 6 };
yield return new object[] { subject.GetCurrentPIN(secretAsBase32, DateTime.UtcNow, true), 7 };
}

public static IEnumerable<object[]> GetPins(HashType hashType)
{
var subject = new TwoFactorAuthenticator(hashType);

yield return new object[] { subject.GetCurrentPIN(secret), 2 };
yield return new object[] { subject.GetCurrentPIN(secret, DateTime.UtcNow), 3 };
yield return new object[] { subject.GetCurrentPIN(secretAsBytes), 4 };
yield return new object[] { subject.GetCurrentPIN(secretAsBytes, DateTime.UtcNow), 5 };
yield return new object[] { subject.GetCurrentPIN(secretAsBase32, true), 6 };
yield return new object[] { subject.GetCurrentPIN(secretAsBase32, DateTime.UtcNow, true), 7 };
}
}
}
Loading

0 comments on commit 585a5b1

Please sign in to comment.