以下是我对RFC 6238的简单实现
using System.Text;
using System.Security.Cryptography;
namespace totp;
class Program
{
static void Main(string[] args)
{
//string secret = "12345678901234567890"; // HOTP
string secret = "3132333435363738393031323334353637383930"; // TOTP
while (true)
{
long unix = 1111111109;
long unixRoundedRaw = (long)Math.Floor(unix / 30.0);
long unixRounded = (long)Math.Floor(unix / 30.0) * 30;
// unix -= unix % 30;
Console.WriteLine("unix: " + unix);
Console.WriteLine("time: " + DateTimeOffset.FromUnixTimeSeconds(unix).DateTime);
Console.WriteLine("tron: " + DateTimeOffset.FromUnixTimeSeconds(unixRounded).DateTime);
Console.WriteLine("totp: " + generateTOTP(secret, unixRoundedRaw, 6));
Console.WriteLine("~~~~~~~~~~~~~~~~~~\n");
Thread.Sleep(1000);
}
}
static string generateTOTP(string secret, long time, int digits)
{
string result = "";
// while (time.Length < 16) { time = "0" + time; }
//byte[] msg = { 0, 0, 0, 0, 0, 0, 0, 2 }; // HOTP
byte[] msg = BitConverter.GetBytes(time); // TOTP
//if (BitConverter.IsLittleEndian) { Array.Reverse(msg); }
Console.WriteLine("hext: " + BitConverter.ToString(msg).Replace("-", ""));
byte[] key = Encoding.ASCII.GetBytes(secret);
//if (BitConverter.IsLittleEndian) { Array.Reverse(key); }
HMACSHA1 hmac_sha = new HMACSHA1(key);
byte[] hash = hmac_sha.ComputeHash(msg);
//if (BitConverter.IsLittleEndian) { Array.Reverse(hash); }
Console.WriteLine("hash: " + BitConverter.ToString(hash).Replace("-", ""));
int offset = hash[^1] & 0x0f;
int bin_code = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| (hash[offset + 3] & 0xff);
result = (bin_code % Math.Pow(10, digits)).ToString();
while (result.Length < digits) { result = "0" + result; }
return result;
}
}
我留下了一些注释行,这些行用于测试工作正常的HOTP(RFC 4226)。输出的数字与规格表中的示例相匹配:
根据RFC4266施加:
Truncated
Count Hexadecimal Decimal HOTP
0 4c93cf18 1284755224 755224
1 41397eea 1094287082 287082
2 82fef30 137359152 359152
我的代码:
hext: 0000000000000002
hash: 0BACB7FA082FEF30782211938BC1C5E70416FF44
totp: 359152
然而,当运行我的代码生成TOTP代码时,时间匹配,T的十六进制值匹配,但TOTP不正确。
从RFC6238开始
+-------------+--------------+------------------+----------+--------+
| Time (sec) | UTC Time | Value of T (hex) | TOTP | Mode |
+-------------+--------------+------------------+----------+--------+
| 59 | 1970-01-01 | 0000000000000001 | 94287082 | SHA1 |
| | 00:00:59 | | | |
| 1111111109 | 2005-03-18 | 00000000023523EC | 07081804 | SHA1 |
| | 01:58:29 | | | |
我的代码:
unix: 1111111109
time: 18/03/2005 01:58:29
tron: 18/03/2005 01:58:00
hext: EC23350200000000
hash: 0AE6677BAD3CD817B337F34E37AEC0D12EED7CC4
totp: 962199
~~~~~~~~~~~~~~~~~~
反转T的值(切换到Big endian)不会产生与示例匹配的输出,这就是为什么我对这些行进行了注释。我确信时间的ASCII编码不是问题所在,因为t的十六进制值与示例相匹配。我看到一些人建议秘密应该在Base32中编码,但这似乎是为了将他们的实现与Google Authenticator相匹配,而不是实际的规范。