Java与Golang在HOTP(rfc-4226)方面的比较

5

我正在尝试在Golang中实现HOTP(rfc-4226),但我无法生成有效的HOTP。我可以在Java中生成它,但由于某种原因,在Golang中实现不同。以下是示例:

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) 不同。
在Java中,生成的字节数组如下: 83 -116 -9 -98 115 -126 -3 -48 ,而在Go中是: 83 140 247 158 115 130 253 207
有没有人知道这两行代码的区别以及如何将Java代码转换成Go?
1个回答

10

在Java中,byte类型是有符号的,范围为-128..127,而在Go中,byteuint8的别名,范围为0..255。因此,如果要比较结果,需要将Java中的负值左移256(加上256)。

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

将负的Java byte值加上256后,输出与Go的输出几乎相同:最后一个字节差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,但你没有发布它。

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

提示2:要以有符号的方式显示Go的字节,只需将它们转换为int8(即带符号的)即可,例如:

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 

1
哇 - TIL!那么相反的操作会是什么?(将Go版本转换为Java版本)。非常感谢! - Zach Kauffman
1
@ZachKauffman:它是相同的数据,只是解释为有符号或无符号的差别。 - JimB
1
@ZachKauffman 是的,要以有符号的方式显示Go byte值,只需将其转换为int8。请参见编辑后的答案。 - icza
太棒了!看来我需要再复习一下基础知识(开个玩笑)。在 Go 版本中,将返回值从 base64.StdEncoding.EncodeToString(h.Sum(nil)) 改为 hex.EncodeToString(h.Sum(nil)) 似乎解决了这个问题。 - Zach Kauffman
2
@ZachKauffman Base64和十六进制编码是完全不同的东西(产生完全不同的结果)。所以基本上,在您的情况下,长->字节数组转换是正确的,但在您的示例中,“counter”值是不同的。 - icza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接