TOTP / HOTP / HmacSHA256 с неподписанным байтовым ключом в Java

Как мы видим из следующих вопросов:

Java HmacSHA256 с ключом

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

Java не очень хорошо работает при использовании ключа в случае использования TOTP / HOTP / HmacSHA256. Мой анализ состоит в том, что следующее вызывает проблемы:

  • String.getBytes будет (конечно) давать отрицательные байтовые значения для символов со значением символа> 127;
  • javax.crypto.Mac а также javax.crypto.spec.SecretKeySpec как внешнее, так и внутреннее использование byte[] для принятия и преобразования ключа.

Мы приобрели несколько однопользовательских OTP-устройств Feitian C-200, и они поставляются с шестнадцатеричным строковым секретом, состоящим из значений байтов> 127.

Мы успешно создали PoC в Ruby для этих токенов, который работает безупречно. Поскольку мы хотим интегрировать их в Keycloak, нам нужно найти решение на Java.

Поскольку каждая реализация TOTP / HOTP / HmacSHA256, которую мы видели, использует javax.crypto библиотека и byte[]мы боимся переписать все используемые классы, но используя int чтобы поддержать этот сценарий.

Q: есть ли другой способ? Как мы можем использовать секреты в вычислениях HmacSHA256 в Java, для которых байты имеют значения> 127, без необходимости переписывать все?


Обновить

Я смотрел в неправильном направлении. Моя проблема заключалась в том, что ключ представлял собой строку (UTF-16 в Java), которая содержала символы Unicode, которые были разбиты на два байта getBytes()перед тем, как перейти в SecretKeySpec,

форсирование StandardCharsets.ISO_8859_1 на этом преобразование исправляет проблему.

2 ответа

Решение

Подписанный против неподписанного является вопросом представления, который главным образом относится только к людям. Компьютер не знает или не заботится 0xFF средства -1 или же 255 тебе. Так что нет, вам не нужно использовать целые, используя byte[] работает просто отлично.

Это не означает, что вы не можете что-то сломать, поскольку некоторые операции работают на основе стандартных переменных со знаком. Например:

byte b = (byte)255;    // b is -1 or 255, depending on how you interpret it
int i = b;      // i is -1 or 2³² instead of 255
int u = b & 0xFF; // u is 255

Многим кажется, что Java имеет только подписанные примитивы (boolean а также char не выдерживаю). Однако Java прекрасно способна выполнять криптографические операции, поэтому все эти вопросы, где что-то "невозможно", являются просто ошибками пользователя. Что не нужно при написании кода, чувствительного к безопасности.

Не бойтесь Java:) Я протестировал десятки токенов от разных производителей, и с Java все в порядке, вам просто нужно подобрать правильный конвертер.

Распространенной проблемой является получение байтов из String как getBytes() вместо использования правильного конвертера. Файл, который вы получили от своего поставщика, представляет собой секретные ключи в шестнадцатеричном формате, поэтому просто наберите "шестнадцатеричная строка в байтовом массиве java" и выберите подходящее решение.

Hex, Base32, Base64 это просто представление, и вы можете легко конвертировать из одного в другое.

Я столкнулся с абсолютно той же проблемой (несколько лет спустя): у нас были устройства Feitian, и нам пришлось настроить их серверный код.
Ни одна из доступных реализаций с ними не работала (ни php, ни java).

Решение: устройства Feitian имеют шестнадцатеричные числа. Сначала вам нужно декодировать семя в необработанный двоичный файл (например, в PHP с использованием hex2bin()). Эти данные являются правильным вводом функций TOTP/HOTP. HEX2BIN () версия Java является немного сложнее, и его решение ясно написано в вопросе ОП.

(Короче говоря: результат hex2bin вы должны интерпретировать с помощью StandardCharsets.ISO_8859_1, иначе некоторые символы будут интерпретированы как 2 байта utf-16 char, что вызывает другой код доступа в конце)

       String hex = "1234567890ABCDEF"; // original seed from Feitian

Sring secretKey = new String(hex2bin(hex), StandardCharsets.ISO_8859_1);
Key key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.ISO_8859_1), "RAW");
// or without String representation:
Key key = new SecretKeySpec(hex2bin(hex), "RAW");
Другие вопросы по тегам