2015-11-06 114 views
1

這將是一個長期的問題,但我有一個非常奇怪的錯誤。我在C++中使用OpenSSL來計算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(); 
} 

一些試驗使用不同的密鑰運行(所有的字符串被解釋爲十六進制):


密鑰1: 736A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBB F117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

消息: 測試

HMAC的OpenSSL: b37f79df52afdbbc4282d3146f9fe7a254dd23b3

HMAC的Java的Mac: b37f79df52afdbbc4282d3146f9fe7a254dd23b3


鍵2:636A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBBF117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

消息: 測試

HMAC的OpenSSL: bac64a905fa6ae3f7bf5131be06ca037b3b498d7

HMAC的Java的Mac: bac64a905fa6ae3f7bf5131be06ca037b3b498d7


重點3:836A66B29072C49AB6DC93BB2BA41A53E169D14621872B0345F01EBBF117FCE48EEEA2409CFC1BD92B0428BA0A34092E3117BEB4A8A14F03391C661994863DAC1A75ED437C1394DA0741B16740D018CA243A800DA25311FDFB9CA4361743E8511E220B79C2A3483FCC29C7A54F1EB804481B2DC87E54A3A7D8A94253A60AC77FA4584A525EDC42BF82AE2A1FD6E3746F626E0AFB211F6984367B34C954B0E08E3F612590EFB8396ECD9AE77F15D5222A6DB106E8325C3ABEA54BB59E060F9EA0

消息: 測試

HMAC運算enSSL: c189c637317b67cee04361e78c3ef576c3530aa7

HMAC的Java的Mac: 472d734762c264bea19b043094ad0416d1b2cd9c

如數據所示,當鑰匙獲取到大,發生錯誤。如果不知道哪個實現有問題。我也嘗試了更大的按鍵和更小的按鍵。我還沒有確定確切的門檻。任何人都可以發現問題嗎?有沒有人能夠通過使用不同的軟件進行仿真來告訴我哪種HMAC在最後一種情況下是不正確的,或者任何人都可以告訴我可以使用哪種第三種實現來檢查我的?

親切的問候,

羅埃爾風暴

+0

我認爲問題不在關鍵的長度,而是在其符號。你可以嘗試用一個「FF」替換一個短鍵的起始字節,看看你在C++和Java之間得到不同的結果嗎? – RealSkeptic

+0

那麼用FF代替83例如? – Silver

+0

這樣做仍然會導致不同的HMAC。 Openssl:53e8fab89762b945f08d245f963aab72dfd47533 Java:15996db38398cb114f73f81645d7bbb87b24c2e4 – Silver

回答

2

當您轉換爲十六進制字符串Java中的BigInt,它假定該數值爲正(除非該字符串包括-號)。

但它的內部表示是二補。這意味着一個位用於標誌。

如果您要轉換的值以007F之間的十六進制開頭,那麼這不是問題。它可以直接轉換字節,因爲最左邊的位是零,這意味着該數字被認爲是正數。

但是,如果您要將一個以80開頭的值轉換爲FF,那麼最左邊的位是1,這將被視爲負值。爲避免出現這種情況,並保持BigInteger的值與其提供的值完全一致,它會在開始時添加另一個零字節。

所以,在內部,一個數字,如7ABCDE的轉化字節數組

0x7a 0xbc 0xde 

但是,許多諸如FABCDE(僅第一個字節是不同!),是的轉化:

0x00 0xfa 0xbc 0xde 

這意味着對於以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

工程就像一個魅力。如果我理解正確,您的代碼正在檢查生成的數組的長度是否正確。如果不是這種情況,它會複製字符1的字節數組,直到數組的末尾,從而縮短1個字節? 我注意到領先的0之前,但我認爲這並不重要,因爲他們是領先0。 – Silver

+0

@RoelStorms是的,正好。當您將數據視爲「數字」時,前導零並不重要。但是,當您將數據視爲數據時 - 要加密和解密 - 則零是合法的數據(例如,考慮黑色像素)。 – RealSkeptic