Last active
November 10, 2022 16:16
-
-
Save Misiu/c2822de165e347ede71750ab20b6c12a to your computer and use it in GitHub Desktop.
Custom implementation of Microsoft's Rfc6238AuthenticationService class from ASP.NET Identity 2.2.1 https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Core/Rfc6238AuthenticationService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal static class MyRfc6238AuthenticationService | |
{ | |
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |
private static readonly TimeSpan DefaultTimeStep = TimeSpan.FromMinutes(3); | |
private static readonly Encoding Encoding = new UTF8Encoding(false, true); | |
private static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier) | |
{ | |
// # of 0's = length of pin | |
const int mod = 1000000; | |
// See https://tools.ietf.org/html/rfc4226 | |
// We can add an optional modifier | |
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); | |
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); | |
// Generate DT string | |
var offset = hash[hash.Length - 1] & 0xf; | |
Debug.Assert(offset + 4 < hash.Length); | |
var binaryCode = (hash[offset] & 0x7f) << 24 | |
| (hash[offset + 1] & 0xff) << 16 | |
| (hash[offset + 2] & 0xff) << 8 | |
| (hash[offset + 3] & 0xff); | |
return binaryCode % mod; | |
} | |
private static byte[] ApplyModifier(byte[] input, string modifier) | |
{ | |
if (String.IsNullOrEmpty(modifier)) | |
{ | |
return input; | |
} | |
var modifierBytes = Encoding.GetBytes(modifier); | |
var combined = new byte[checked(input.Length + modifierBytes.Length)]; | |
Buffer.BlockCopy(input, 0, combined, 0, input.Length); | |
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length); | |
return combined; | |
} | |
// More info: https://tools.ietf.org/html/rfc6238#section-4 | |
private static ulong GetCurrentTimeStepNumber(TimeSpan timeStep) | |
{ | |
var delta = DateTime.UtcNow - UnixEpoch; | |
return (ulong)(delta.Ticks / timeStep.Ticks); | |
} | |
public static int GenerateCode(SecurityToken securityToken, string modifier = null, TimeSpan? timeStep = null) | |
{ | |
if (securityToken == null) | |
{ | |
throw new ArgumentNullException("securityToken"); | |
} | |
// Allow a variance of no greater than 90 seconds in either direction | |
var currentTimeStep = GetCurrentTimeStepNumber(timeStep ?? DefaultTimeStep); | |
using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone())) | |
{ | |
return ComputeTotp(hashAlgorithm, currentTimeStep, modifier); | |
} | |
} | |
public static bool ValidateCode(SecurityToken securityToken, int code, string modifier = null, TimeSpan? timeStep = null) | |
{ | |
if (securityToken == null) | |
{ | |
throw new ArgumentNullException("securityToken"); | |
} | |
// Allow a variance of no greater than time step in either direction | |
var currentTimeStep = GetCurrentTimeStepNumber(timeStep??DefaultTimeStep); | |
using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone())) | |
{ | |
for (var i = -2; i <= 2; i++) | |
{ | |
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); | |
if (computedTotp == code) | |
{ | |
return true; | |
} | |
} | |
} | |
// No match | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment