private static HMAC GetHMAC(OtpKey otpKey, byte[] key) { return(otpKey.Algorithm switch { Algorithms.SHA1 => new HMACSHA1(key), Algorithms.SHA256 => new HMACSHA256(key), Algorithms.SHA512 => new HMACSHA512(key), _ => throw new Exception("Unknown algorighm"), });
/// <summary> /// Get remainint time before the current code will change /// </summary> public static TimeSpan GetRemainingTime(OtpKey otpKey) { if (otpKey.AuthType != AuthTypes.TOTP) { throw new Exception("Remaining time not supported for non-TOTP authentication"); } long periodTicks = SECOND_TICKS * otpKey.Period; return(TimeSpan.FromTicks(periodTicks - (CorrectedTicks % periodTicks) + SECOND_TICKS)); }
/// <summary> /// Get an OTP code /// </summary> public static string GetCode(OtpKey otpKey) { if (otpKey.AuthType == AuthTypes.TOTP) { long challenge = CorrectedTicks / (otpKey.Period * SECOND_TICKS); return(GetCode(otpKey, (ulong)challenge)); } else { return(GetCode(otpKey, otpKey.Counter)); } }
/// <summary> /// Check if the specified code is valid /// </summary> /// <param name="slidingWindow">How many periods before and after the current time to check</param> public static bool CheckCode(OtpKey otpKey, string code, uint slidingWindow = 0) { if (code == GetCode(otpKey)) { return(true); } if (slidingWindow == 0) { return(false); } if (otpKey.AuthType == AuthTypes.TOTP) { long origOffset = m_TimeOffset; long ticks = TimeSpan.FromSeconds(otpKey.Period * slidingWindow).Ticks; m_TimeOffset = origOffset - ticks; bool ret = code == GetCode(otpKey); if (!ret) { m_TimeOffset = origOffset + ticks; ret = code == GetCode(otpKey); } m_TimeOffset = origOffset; return(ret); } if (otpKey.AuthType == AuthTypes.HOTP) { ulong origCounter = otpKey.Counter; otpKey.Counter = origCounter - 1; bool ret = code == GetCode(otpKey); if (!ret) { otpKey.Counter = origCounter + 1; ret = code == GetCode(otpKey); } otpKey.Counter = origCounter; return(ret); } return(false); }
private static string GetCode(OtpKey otpKey, ulong challengeValue) { ulong chlg = challengeValue; byte[] challenge = new byte[8]; for (int j = 7; j >= 0; j--) { challenge[j] = (byte)((int)chlg & 0xff); chlg >>= 8; } var key = Base32Encoding.ToBytes(otpKey.Secret); for (int i = otpKey.Secret.Length; i < key.Length; i++) { key[i] = 0; } using var mac = GetHMAC(otpKey, key); var hash = mac.ComputeHash(challenge); // the last 4 bits of the mac say where the code starts int offset = hash[hash.Length - 1] & 0xf; // extract those 4 bytes byte[] bytes = new byte[4]; Array.Copy(hash, offset, bytes, 0, 4); if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } uint fullcode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff; // we use the last 8 digits of this code in radix 10 uint codemask = (uint)Math.Pow(10, otpKey.Digits); string format = new string('0', (int)otpKey.Digits); string code = (fullcode % codemask).ToString(format); return(code); }