Implementing TOTP
Here's another short post with a code snippet. I have recently stumbled over a post by Drew DeVault about implementing TOTP and I was wondering how his Python example would turn out in C#. Other people have already done a much better job at this (have a look at the Otp.NET library), but why not give it a shot anyway.
Example usage:
var totp = TOTP.Compute("3N2OTFHXKLR2E3WNZSYQ====");
And the implementation:
// TOTP.cs is based on: // - https://github.com/kspearrin/Otp.NET // - https://github.com/susam/mintotp // - https://drewdevault.com/2022/10/18/TOTP-is-easy.html // // Base32.cs is copied from: // - https://github.com/dotnet/aspnetcore/blob/01cc669960821e23ef3275cd5ad81f7192972010/src/Identity/Extensions.Core/src/Base32.cs public static class TOTP { private const int DigitsCount = 6; private const long WindowSeconds = 30L; private const long UnixEpocTicks = 621355968000000000L; private const long TicksToSeconds = 10000000L; public static string Compute(string secret) { return Compute(secret, ToCounter(DateTime.UtcNow)); } private static string Compute(string secret, long counter) { var key = ToKey(secret); var bytes = ToBigEndianBytes(counter); var hash = HMACSHA1.HashData(key, bytes); var offset = hash[^1] & 0x0f; var otp = (hash[offset] & 0x7f) << 24 | (hash[offset + 1] & 0xff) << 16 | (hash[offset + 2] & 0xff) << 8 | (hash[offset + 3] & 0xff) % 1000000; return ToStringCode(otp); } private static byte[] ToKey(string secret) { return Base32.FromBase32( secret.ToUpper().PadRight(32, '=')); } private static byte[] ToBigEndianBytes(long value) { var bytes = BitConverter.GetBytes(value); Array.Reverse(bytes); return bytes; } private static string ToStringCode(int value) { var truncated = value % (int)Math.Pow(10, DigitsCount); return truncated.ToString().PadLeft(DigitsCount, '0'); } private static long ToCounter(DateTime timeStamp) { var unixTimeStamp = (timeStamp.Ticks - UnixEpocTicks) / TicksToSeconds; return unixTimeStamp / WindowSeconds; } }