用于 HOTP 的 Java 与 Golang (rfc-4226)

2022-01-10 00:00:00 cryptography sha512 java go

我正在尝试在 Golang 中实现 HOTP (rfc-4226),并且正在努力生成有效的 HOTP.我可以在 java 中生成它,但由于某种原因,我在 Golang 中的实现有所不同.以下是示例:

I'm trying to implement HOTP (rfc-4226) in Golang and I'm struggling to generate a valid HOTP. I can generate it in java but for some reason my implementation in Golang is different. Here are the samples:

public static String constructOTP(final Long counter, final String key)
        throws NoSuchAlgorithmException, DecoderException, InvalidKeyException {
    final Mac mac = Mac.getInstance("HmacSHA512");

    final byte[] binaryKey = Hex.decodeHex(key.toCharArray());

    mac.init(new SecretKeySpec(binaryKey, "HmacSHA512"));
    final byte[] b = ByteBuffer.allocate(8).putLong(counter).array();
    byte[] computedOtp = mac.doFinal(b);

    return new String(Hex.encodeHex(computedOtp));
}

在 Go 中:

func getOTP(counter uint64, key string) string {
    str, err := hex.DecodeString(key)
    if err != nil {
        panic(err)
    }
    h := hmac.New(sha512.New, str)
    bs := make([]byte, 8)
    binary.BigEndian.PutUint64(bs, counter)
    h.Write(bs)
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

我认为问题在于 Java 行:ByteBuffer.allocate(8).putLong(counter).array(); 生成的字节数组与 Go 行不同:binary.BigEndian.PutUint64(bs, counter).

I believe the issue is that the Java line: ByteBuffer.allocate(8).putLong(counter).array(); generates a different byte array than the Go line: binary.BigEndian.PutUint64(bs, counter).

在 Java 中,生成以下字节数组:83 -116 -9 -98 115 -126 -3 -48 而在 Go 中:83 140 247 158 115 130 253 207.

In Java, the following byte array is generated: 83 -116 -9 -98 115 -126 -3 -48 and in Go: 83 140 247 158 115 130 253 207.

有人知道这两条线的区别以及如何移植 java 线吗?

Does anybody know the difference in the two lines and how I can port the java line to go?

推荐答案

Java中的byte类型是有符号的,它的范围是-128..127,而在 Go 中,byteuint8 的别名,范围为 0..255.所以如果你想比较结果,你必须将Java的负值移动256(添加256).

The byte type in Java is signed, it has a range of -128..127, while in Go byte is an alias of uint8 and has a range of 0..255. So if you want to compare the results, you have to shift negative Java values by 256 (add 256).

提示:要以无符号方式显示 Java byte 值,请使用:byteValue &0xff 使用 byte 的 8 位作为 int 中的最低 8 位将其转换为 int.或者更好:以十六进制形式显示两个结果,这样您就不必关心符号...

Tip: To display a Java byte value in an unsigned fashion, use: byteValue & 0xff which converts it to int using the 8 bits of the byte as the lowest 8 bits in the int. Or better: display both results in hex form so you don't have to care about sign-ness...

将 256 添加到负 Java 字节值,输出几乎与 Go 相同:最​​后一个字节减 1:

Adding 256 to your negative Java byte values, the output is almost identical to Go's: the last byte is off by 1:

javabytes := []int{83, -116, -9, -98, 115, -126, -3, -48}
for i, b := range javabytes {
    if b < 0 {
        javabytes[i] += 256
    }
}
fmt.Println(javabytes)

输出是:

[83 140 247 158 115 130 253 208]

因此,Java 数组的最后一个字节是 208,而 Go 数组的最后一个字节是 207.我猜您的 counter 会在您未发布的代码中的其他地方增加一次.

So the last byte of your Java array is 208 while Go's is 207. I'm guessing your counter is incremented once somewhere else in your code which you haven't posted.

不同的是,在 Java 中返回的是十六进制编码的结果,而在 Go 中返回的是 Base64 编码的结果(它们是两种不同的编码,给出完全不同的结果).正如您所确认的,在返回 hex.EncodeToString(h.Sum(nil)) 的 Go 中,结果匹配.

What differs is that in Java you return the hex encoded result while in Go you return the Base64 encoded result (they are 2 different encodings giving entirely different results). As you confirmed, in Go returning hex.EncodeToString(h.Sum(nil)) the results match.

提示 #2:要以签名方式显示 Go 的字节,只需将它们转换为 int8(已签名),如下所示:

Tip #2: To display Go's bytes in a signed fashion, simply convert them to int8 (which is signed) like this:

gobytes := []byte{83, 140, 247, 158, 115, 130, 253, 207}
for _, b := range gobytes {
    fmt.Print(int8(b), " ")
}

这个输出:

83 -116 -9 -98 115 -126 -3 -49 

相关文章