Java против Golang для HOTP (rfc-4226)

Я пытаюсь реализовать HOTP (rfc-4226) в Golang, и я изо всех сил пытаюсь создать действительный 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, а в Go: 83 140 247 158 115 130 253 207.

Кто-нибудь знает разницу в двух строках и как я могу перенести строку java?


person Zach Kauffman    schedule 13.12.2017    source источник


Ответы (1)


Тип byte в Java является подписанным, имеет диапазон -128..127, тогда как в Go 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, а 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 
person icza    schedule 13.12.2017
comment
вау - ТИЛ! какой будет обратная операция? (взяв версию Go и создав версию Java). Большое спасибо! - person Zach Kauffman; 13.12.2017
comment
@ZachKauffman: это те же данные, просто разница между их интерпретацией как подписанной или неподписанной. - person JimB; 13.12.2017
comment
@ZachKauffman Да, чтобы отобразить значение Go byte со знаком, просто преобразуйте его в int8. Смотрите отредактированный ответ. - person icza; 13.12.2017
comment
Потрясающий! Думаю, мне нужно немного освежить свои основы (каламбур). Похоже, изменение моего возврата с base64.StdEncoding.EncodeToString(h.Sum(nil)) на hex.EncodeToString(h.Sum(nil)) в версии Go решило проблему. - person Zach Kauffman; 13.12.2017
comment
@ZachKauffman Base64 и шестнадцатеричное кодирование - совершенно разные вещи (дающие совершенно разные результаты). Таким образом, в вашем случае преобразование длинного массива байтов правильно, но в ваших примерах значение counter было другим. - person icza; 13.12.2017