2015-11-06 18 views
1

Это будет длинный вопрос, но у меня действительно странная ошибка. Я использую OpenSSL на C++ для вычисления HMAC и сравниваю их с имитационной реализацией с использованием javax.crypto.Mac. Для некоторых ключей расчет HMAC правильный, а для других - разница в HMAC. Я считаю, что проблема возникает, когда ключи становятся большими. Вот подробности.Java Mac HMAC vs C++ OpenSSL hmac

Вот наиболее важный код C++:

void computeHMAC(std::string message, std::string key){ 
    unsigned int digestLength = 20; 
    HMAC_CTX hmac_ctx_; 
    BIGNUM* key_ = BN_new();; 

    BN_hex2bn(&key_, key); 

    unsigned char convertedKey[BN_num_bytes(key_)]; 
    BIGNUM* bn = BN_new(); 

    HMAC_CTX_init(&hmac_ctx_); 

    BN_bn2bin(bn, convertedKey); 
    int length = BN_bn2bin(key_, convertedKey); 

    HMAC_Init_ex(&hmac_ctx_, convertedKey, length, EVP_sha1(), NULL); 

/*Calc HMAC */ 
    std::transform(message.begin(), message.end(), message.begin(), ::tolower); 
    unsigned char digest[digestLength]; 

    HMAC_Update(&hmac_ctx_, reinterpret_cast<const unsigned char*>(message.c_str()), 
     message.length()); 
    HMAC_Final(&hmac_ctx_, digest, &digestLength); 
    char mdString[40]; 
    for(unsigned int i = 0; i < 20; ++i){ 
     sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]); 
    } 
    std::cout << "\n\nMSG:\n" << message << "\nKEY:\n" + std::string(BN_bn2hex(key_)) + "\nHMAC\n" + std::string(mdString) + "\n\n"; 
} 

Тест Java выглядит следующим образом:

public String calculateKey(String msg, String key) throws Exception{ 

    HMAC = Mac.getInstance("HmacSHA1"); 

    BigInteger k = new BigInteger(key, 16); 

    HMAC.init(new SecretKeySpec(k.toByteArray(), "HmacSHA1")); 

    msg = msg.toLowerCase(); 
    HMAC.update(msg.getBytes()); 
    byte[] digest = HMAC.doFinal(); 

    System.out.println("Key:\n" + k.toString(16) + "\n"); 
    System.out.println("HMAC:\n" + DatatypeConverter.printHexBinary(digest).toLowerCase() + "\n"); 

    return DatatypeConverter.printHexBinary(digest).toLowerCase(); 
} 

Некоторые тест работает с разными ключами (все строки интерпретируются как гекса):


Key1: 736A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBB F117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

Msg: тест

HMAC OpenSSL: b37f79df52afdbbc4282d3146f9fe7a254dd23b3

HMAC Java Mac: b37f79df52afdbbc4282d3146f9fe7a254dd23b3


Key 2: 636A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBBF117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

Msg: тест

HMAC OpenSSL: bac64a905fa6ae3f7bf5131be06ca037b3b498d7

HMAC Java Mac: bac64a905fa6ae3f7bf5131be06ca037b3b498d7


Key 3: 836A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBBF117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

Msg: тест

HMAC Op enSSL: c189c637317b67cee04361e78c3ef576c3530aa7

HMAC Java Mac: 472d734762c264bea19b043094ad0416d1b2cd9c

Как показывают данные, когда ключ попадает в большой, возникает ошибка. Если вы не знаете, какая реализация неисправна.Я также пытался с большими клавишами и меньшими клавишами. Я не определил точный порог. Может ли кто-нибудь обнаружить проблему? Кто-нибудь может сказать мне, какой HMAC неверен в последнем случае, выполнив симуляцию с использованием другого программного обеспечения или кто-нибудь скажет мне, какую 3-ю реализацию я мог бы использовать для проверки моего?

С наилучшими пожеланиями,

Рул циклоны

+0

Я думаю, что проблема не в длине ключа, а в его знаке. Можете ли вы попытаться заменить начальный байт одного из коротких клавиш, которые работают с «FF», и посмотреть, есть ли у вас разные результаты между C++ и Java? – RealSkeptic

+0

Так, например, замените 83 на FF? – Silver

+0

Выполнение этого результата приводит к другому HMAC. Openssl: 53e8fab89762b945f08d245f963aab72dfd47533 Java: 15996db38398cb11bf3f81645d7bbb87b24c2e4 – Silver

ответ

2

При преобразовании шестнадцатеричной строки в BigInt в Java, то предполагается, что число положительное (если строка не содержит - знака).

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

Если вы преобразовываете значение, начинающееся с шестнадцатеричного значения между 00 и 7F включительно, то это не проблема. Он может напрямую преобразовать байт, потому что самый левый бит равен нулю, а это означает, что число считается положительным.

Но если вы преобразовываете значение, которое начинается с 80 через FF, то самый левый бит равен 1, что считается отрицательным. Чтобы этого избежать, и сохраняйте значение BigInteger точно так же, как оно поставляется, оно добавляет еще один нулевой байт в начале.

Таким образом, внутренне, преобразование числа таких, как 7ABCDE является массив

0x7a 0xbc 0xde 

Но превращение ряда таких как FABCDE (только первый байт отличается!), Является:

Это означает, что для ключей, начинающихся с байта в диапазоне 80-FF, BigInteger.toByteArray() не производит тот же массив, что и ваша C++-программа, но массив длиной один байт.

Существует несколько способов обойти это - например, использовать собственный синтаксический анализатор с шестнадцатеричным байтом или найти существующий в некоторой библиотеке. Если вы хотите использовать один произведенный BigInteger, вы могли бы сделать что-то вроде этого:

BigInteger k = new BigInteger(key, 16); 
byte[] kByteArr = k.toByteArray(); 
if (kByteArr.length > (key.length() + 1)/2) { 
    kByteArr = Arrays.copyOfRange(kByteArr,1,kByteArr.length); 
} 

Теперь вы можете использовать kByteArr выполнить операцию правильно.

Другой вопрос, на который вы должны обратить внимание - это ключи, длина которых нечетна. В общем случае вы не должны иметь шестнадцатеричную октетную строку с нечетной длиной. Строка вроде F8ACB на самом деле 0F8ACB (которая не собирается вызывать дополнительный байт в BigInteger) и должна интерпретироваться как таковая. Вот почему я написал (key.length() + 1) в моей формуле - если ключ нечетный, его следует интерпретировать как один октет дольше. Это также важно, если вы напишете собственный конвертер с шестнадцатеричным байтом - если длина нечетная, вы должны добавить нуль в начале, прежде чем начать конвертирование.

+0

Я собираюсь проверить это завтра, так как я должен идти сейчас, но вы можете описать поведение совершенно, поэтому я предполагаю, что это действительно правильный ответ. – Silver

+0

Работает как очарование. Если я правильно понял, ваш код проверяет правильность длины созданного массива. Если это не так, он копирует bytearray с символа 1 до конца массива, тем самым сокращая его на 1 байт? Я заметил ведущие 0, но я думал, что это не имеет значения, так как они были ЛИДИНГОМ 0. – Silver

+0

@RoelStorms Да, точно. Ведущие нули не важны, когда вы смотрите на данные как «число». Но когда вы смотрите на данные как на данные, которые должны быть зашифрованы и дешифрованы, то нуль - это законная часть данных (например, черный пиксель). – RealSkeptic