代码之家  ›  专栏  ›  技术社区  ›  birb

RFC-6238 TOTP实现与示例不匹配

  •  0
  • birb  · 技术社区  · 1 年前

    以下是我对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相匹配,而不是实际的规范。

    0 回复  |  直到 1 年前
        1
  •  0
  •   birb    1 年前

    感谢Topaco的建议——我浏览了每个中间变量,发现我的秘密正在被编码,而不是作为一个字节的文本数组。使用此函数对此进行分析解决了问题:

        static public IEnumerable<byte> GetBytesFromByteString(string s)
        {
            for (int index = 0; index < s.Length; index += 2)
            {
                yield return Convert.ToByte(s.Substring(index, 2), 16);
            }
        }
    

    我还不得不把钥匙换成Big endian。 我的代码现在符合SHA1的规范 非常感谢。