算法二因素认证不起作用

问题描述 投票:0回答:1

我尝试根据 RFC 6238 (https://datatracker.ietf.org/doc/html/rfc6238) 使用 VB.NET 构建自己的 2FA 应用程序。

我设法重现了论文附录的结果,但是当我尝试使用真正的 2FA 移动应用程序对其进行测试时,我得到了不同的结果。

我向 ChatGPT 寻求帮助(“生成一个像 google 身份验证器一样计算密钥的代码”),但返回的代码段也没有帮助。我在网上找到的很少的提示表明文本的初始编码可能存在问题。 “Base32”似乎是所需的编码,但我无法理解这到底意味着什么。

适用于 IOS 和 Android 的 FreeOTP 应用程序 (https://github.com/freeotp) 需要不同的输入密码。两个应用程序的文本框中都显示“Base32”,但虽然在 IOS 上无法输入 0、1、8 或 9,但在 Android 上绝对可以。

那么 Base32 在这种情况下意味着什么?密钥必须仅包含 [A-Z2-7],还是算法会对任何字母数字输入执行某些操作?

这是我的代码:

   Public Function GenerateTOTP(secretKey As String, stepwidth As Long, stepdeviation As Integer, dt As DateTime, digits As UInteger) As String
       Dim unixEpoch As Long = Convert.ToInt64((dt - New DateTime(1970, 1, 1)).TotalSeconds)
        Dim steps As Long = Math.Floor(unixEpoch / stepwidth) + stepdeviation
        Dim secretKeyBytes As Byte() = Encoding.UTF8.GetBytes(secretKey.ToUpper)
        If (BitConverter.IsLittleEndian) Then Array.Reverse(secretKeyBytes) 'For RFC 6238 appendix, this line needs to be disabled

        Dim hmac As New HMACSHA1(secretKeyBytes)
        Dim stepbytes As Byte() = BitConverter.GetBytes(steps)
        If (BitConverter.IsLittleEndian) Then Array.Reverse(stepbytes)
        Dim hash As Byte() = hmac.ComputeHash(stepbytes)
        Dim offset As Integer = hash(hash.Length - 1) And 15

        'Additional check
        Dim binaryArray(3) As Byte
        Array.Copy(hash, offset, binaryArray, 0, 4)
        If (BitConverter.IsLittleEndian) Then Array.Reverse(binaryArray)
        Dim binaryBA As UInteger = BitConverter.ToUInt32(binaryArray, 0)

        Dim binary As UInteger = ((hash(offset) And 127) << 24) Or ((hash(offset + 1) And 255) << 16) Or ((hash(offset + 2) And 255) << 8) Or (hash(offset + 3) And 255)

        Dim totp As UInteger = binary Mod (10 ^ digits)
        Dim totpBA As UInteger = binaryBA Mod (10 ^ digits)
        Console.WriteLine("-------------------------------------------------------")
        Console.WriteLine("Key:                   " & secretKey)
        Console.WriteLine("Date:                  " & dt.ToString("dd.MM.yyyy HH:mm:ss"))
        Console.WriteLine("unixEpoch:             " & unixEpoch)
        Console.WriteLine("steps:                 " & steps)
        Console.WriteLine("Binary Norm UInt:      " & totp.ToString().PadLeft(digits, "0"c))
        Console.WriteLine("Binary Norm UInt check:" & totpBA.ToString().PadLeft(digits, "0"c))

        Return totp.ToString().PadLeft(digits, "0"c) & " " & totpBA.ToString().PadLeft(digits, "0"c)
    End Function

我猜主要问题在于 UTF8 编码,但我不知道应该如何将字符串转换为 Base32 兼容的内容。我尝试了不同的编码,使用大端和小端,更改了位,但没有任何效果。

RFC 6238 包含一个我不理解的类。 SecretKeySpec 的作用是什么?关键代码段没有显示任何使用 Base32 的提示。甚至测试键也包含0、1、8和9。

这是 RFC 6238 的摘要:

 private static byte[] hexStr2Bytes(String hex){
     // Adding one byte to get the right conversion
     // Values starting with "0" can be converted
     byte[] bArray = new BigInteger("10" + hex,16).toByteArray();   ' <--------- standard hex from ASCII or UTF8

     // Copy all the REAL bytes, not the "first"
     byte[] ret = new byte[bArray.length - 1];
     for (int i = 0; i < ret.length; i++)
         ret[i] = bArray[i+1];
     return ret;
 }

 private static byte[] hmac_sha(String crypto, byte[] keyBytes,
         byte[] text){
     try {
         Mac hmac;
         hmac = Mac.getInstance(crypto);
         SecretKeySpec macKey =                                ' <--------- no idea
             new SecretKeySpec(keyBytes, "RAW");
         hmac.init(macKey);
         return hmac.doFinal(text);
     } catch (GeneralSecurityException gse) {
         throw new UndeclaredThrowableException(gse);
     }
 }

 public static String generateTOTP(String key,
         String time,
         String returnDigits,
         String crypto){
     int codeDigits = Integer.decode(returnDigits).intValue();
     String result = null;

     // Using the counter
     // First 8 bytes are for the movingFactor
     // Compliant with base RFC 4226 (HOTP)
     while (time.length() < 16 )
         time = "0" + time;

     // Get the HEX in a Byte[]
     byte[] msg = hexStr2Bytes(time);
     byte[] k = hexStr2Bytes(key);
     byte[] hash = hmac_sha(crypto, k, msg);          ' <--------- just "normal" bytes, no Base32

     // put selected bytes into result int
     int offset = hash[hash.length - 1] & 0xf;

     int binary =
         ((hash[offset] & 0x7f) << 24) |
         ((hash[offset + 1] & 0xff) << 16) |
         ((hash[offset + 2] & 0xff) << 8) |
         (hash[offset + 3] & 0xff);

     int otp = binary % DIGITS_POWER[codeDigits];

     result = Integer.toString(otp);
     while (result.length() < codeDigits) {
         result = "0" + result;
     }
     return result;
 }

奇怪的是,当我使用测试键“12345678901234567890”时,我得到了论文的结果,但是FreeOTP应用程序返回了不同的东西(我改变了手机时间)。在 IOS 上我什至无法输入密钥(如上所述)。我检查的每个 2FA 应用程序都会返回相同的结果并引用 RFC 6238,但该算法对我不起作用。

那么我做错了什么? 我是否必须在代码中包含 Base32-ish 的内容?如果是这样,编码到底应该做什么? SecretKeySpec 的作用是什么? 为什么IOS和Android算法有差异?

vb.net two-factor-authentication totp google-2fa
1个回答
0
投票

与ChatGPT的对话终于给了我答案。这是几个错误的混合体:

  1. 我不明白 RFC 6238 只解释了哈希字节的算法,但没有解释字节本身的创建或编码。
  2. 密钥必须由 Base32 字母组成,以匹配网站生成的密钥的常见组成。可以创建另一组字符,但您需要 [A-Z2-7] 才能符合规范。
  3. UTF-8 或 ASCII 字符串需要转换为 Base32 字母表。索引号必须分为 5 位组,然后重新排列为 8 位以创建多个字节。
  4. 大端和小端对结果影响很大。 CharGPT 和一些在线帮助混淆了字节序。

我终于能够创建一个工人阶级了:

Public Class TOTP
    Private Const Base32Alphabet As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"

    Public Function GenerateSecretKey(length As Byte) As String
        Dim rnd As New Random
        Dim output As String = ""
        For i = 0 To length - 1
            output &= Base32Alphabet(rnd.Next(32)).ToString
        Next
        Return output
    End Function

    Public Function GenerateTOTP(secretKey As String, stepwidth As Long, stepdeviation As Integer, dt As DateTime, digits As UInteger) As String
        Dim unixEpoch As Long = Convert.ToInt64((dt - New DateTime(1970, 1, 1)).TotalSeconds)
        Dim steps As Long = Math.Floor(unixEpoch / stepwidth) + stepdeviation

        Dim secretKeyBytes As Byte() = Base32StringToByteArray(secretKey.ToUpper)

        Dim hmac As New HMACSHA1(secretKeyBytes)
        Dim stepbytes As Byte() = BitConverter.GetBytes(steps)
        If (BitConverter.IsLittleEndian) Then Array.Reverse(stepbytes)
        Dim hash As Byte() = hmac.ComputeHash(stepbytes)
        Dim offset As Integer = hash(hash.Length - 1) And 15

        Dim binary As Integer = ((hash(offset) And 127) << 24) Or ((hash(offset + 1) And 255) << 16) Or ((hash(offset + 2) And 255) << 8) Or (hash(offset + 3) And 255)
        Dim totp As Integer = binary Mod (10 ^ digits)

        Return totp.ToString().PadLeft(digits, "0"c)
    End Function

    Private Function Base32StringToByteArray(base32String As String) As Byte()
        Dim bitString As New StringBuilder()

        For Each character In base32String
            Dim index As Integer = Base32Alphabet.IndexOf(Char.ToUpper(character))

            If index < 0 Then
                Throw New ArgumentException("Invalid character")
            End If

            bitString.Append(Convert.ToString(index, 2).PadLeft(5, "0"c))
        Next

        Dim byteList As New List(Of Byte)()

        For i As Integer = 0 To bitString.Length - 1 Step 8
            If i + 8 <= bitString.Length Then
                byteList.Add(Convert.ToByte(bitString.ToString().Substring(i, 8), 2))
            End If
        Next

        Return byteList.ToArray()
    End Function

End Class
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.