Ява против Голанга для HOTP (rfc-4226)

Я пытаюсь реализовать 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));
}

и в го:

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 и в го: 83 140 247 158 115 130 253 207,

Кто-нибудь знает разницу в двух линиях и как я могу портировать Java-линию, чтобы пойти?

1 ответ

Решение

byte тип в Java подписан, у него есть диапазон -128..127 в то время как в го byte псевдоним uint8 и имеет ряд 0..255, Так что если вы хотите сравнить результаты, вы должны сдвинуть отрицательные значения Java на 256 (добавлять 256).

Совет: для отображения Java byte значение без знака, используйте: byteValue & 0xff который преобразует его в int используя 8 битов byte как младшие 8 бит в int , Или лучше: отображать оба результата в шестнадцатеричной форме, так что вам не нужно заботиться о подписи...

Добавляя 256 к вашим отрицательным значениям байтов Java, вывод почти идентичен выводам 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 в то время как Го 207, Я угадываю твои counter увеличивается в другом месте в вашем коде, который вы не опубликовали.

Отличие состоит в том, что в Java вы возвращаете результат в шестнадцатеричном коде, а в Go вы возвращаете результат в кодировке Base64 (это 2 разных кодировки, дающие совершенно разные результаты). Как вы подтвердили, в 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 
Другие вопросы по тегам