TOTP / HOTP / HmacSHA256 с неподписанным байтовым ключом в Java
Как мы видим из следующих вопросов:
Ява против Голанга для 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");