2008-11-26 36 views
2

我試圖解決的情況解碼:在我的可可應用程序,我需要一個字符串對稱密碼加密,張貼到PHP,並讓該腳本解碼數據。該過程需要反向工作以返回答案(PHP編碼,Cocoa解碼)。對數據進行加密,在PHP(反之亦然)

我失去了一些東西,因爲即使我可以同時獲得密鑰和初始化向量(IV)是在PHP和可可相同,當一個應用程序發送它的編碼數據提供給其他解碼永遠不會奏效。兩者都可以很好地對他們自己的數據進行編碼/解碼(驗證以確保手邊沒有一些PEBKAC問題)。我懷疑某處存在填充問題,我只是沒有看到它。

我的可可應用編碼使用SSCrypto(這大約是OpenSSL的功能只是一個方便,花花公子包裝)。密碼是Blowfish,模式是CBC。 (原諒內存泄漏,代碼已被剝離至裸要領)然後

NSData *secretText = [@"secretTextToEncode" dataUsingEncoding:NSUTF8StringEncoding]; 
NSData *symmetricKey = [@"ThisIsMyKey" dataUsingEncoding:NSUTF8StringEncoding]; 

unsigned char *input = (unsigned char *)[secretText bytes]; 
unsigned char *outbuf; 
int outlen, templen, inlen; 
inlen = [secretText length]; 

unsigned char evp_key[EVP_MAX_KEY_LENGTH] = {"\0"}; 
int cipherMaxIVLength = EVP_MAX_IV_LENGTH; 
EVP_CIPHER_CTX cCtx; 
const EVP_CIPHER *cipher = EVP_bf_cbc(); 

cipherMaxIVLength = EVP_CIPHER_iv_length(cipher); 
unsigned char iv[cipherMaxIVLength]; 

EVP_BytesToKey(cipher, EVP_md5(), NULL, [symmetricKey bytes], [symmetricKey length], 1, evp_key, iv); 

NSData *initVector = [NSData dataWithBytes:iv length:cipherMaxIVLength]; 

EVP_CIPHER_CTX_init(&cCtx); 

if (!EVP_EncryptInit_ex(&cCtx, cipher, NULL, evp_key, iv)) { 
    EVP_CIPHER_CTX_cleanup(&cCtx); 
    return nil; 
} 
int ctx_CipherKeyLength = EVP_CIPHER_CTX_key_length(&cCtx); 
EVP_CIPHER_CTX_set_key_length(&cCtx, ctx_CipherKeyLength); 

outbuf = (unsigned char *)calloc(inlen + EVP_CIPHER_CTX_block_size(&cCtx), sizeof(unsigned char)); 

if (!EVP_EncryptUpdate(&cCtx, outbuf, &outlen, input, inlen)){ 
    EVP_CIPHER_CTX_cleanup(&cCtx); 
    return nil; 
} 
if (!EVP_EncryptFinal(&cCtx, outbuf + outlen, &templen)){ 
    EVP_CIPHER_CTX_cleanup(&cCtx); 
    return nil; 
} 
outlen += templen; 
EVP_CIPHER_CTX_cleanup(&cCtx); 

NSData *cipherText = [NSData dataWithBytes:outbuf length:outlen]; 

NSString *base64String = [cipherText encodeBase64WithNewlines:NO]; 
NSString *iv = [initVector encodeBase64WithNewlines:NO]; 

base64String和iv發佈到PHP試圖對其進行解碼:

<?php 

import_request_variables("p", "p_"); 

if($p_data != "" && $p_iv != "") 
{ 
    $encodedData = base64_decode($p_data, true); 
    $iv = base64_decode($p_iv, true); 

    $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, ''); 
    $keySize = mcrypt_enc_get_key_size($td); 
    $key = substr(md5("ThisIsMyKey"), 0, $keySize); 

    $decodedData = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encodedData, MCRYPT_MODE_CBC, $iv); 
    mcrypt_module_close($td); 

    echo "decoded: " . $decodedData; 
} 
?> 

decodedData總是亂碼。

我試過顛倒過程,從PHP發送編碼輸出到可可,但EVP_DecryptFinal()失敗,這是什麼導致我相信有一個NULL填充問題的地方。我已經閱讀並重新閱讀了PHP和OpenSSL文檔,但現在它們都變得模糊起來,並且我沒有想法嘗試。

+0

[OpenSSL 1.1.0c更改了摘要算法](http://stackoverflow.com/q/39637388/608639)在某些內部組件中使用。以前使用MD5,1.1.0切換到SHA256。請注意,這些更改不會影響`EVP_BytesToKey`和像`openssl enc`這樣的命令。 – jww 2017-01-26 16:24:14

回答

3

我認爲你的問題是從密鑰字符串中導出原始加密密鑰的方法在兩側是不同的。 php md5()函數返回一個十六進制字符串,即'a476c3 ...',您將其縮減爲密鑰大小,而EVP_BytesToKey()是一個相當複雜的哈希例程,它返回一個原始字節字符串。它可能會提供的參數簡化爲原始MD5哈希值,但我無法確定。無論哪種方式,它將不同於PHP哈希。

如果您更改PHP來的MD5( 「ThisIsMyKey」,TRUE),這會給你一個原始MD5哈希值。在事情的可可方面,SSCrypto的+ getMD5ForData:方法應該爲同一個字符串生成相同的字符串(不考慮文本編碼問題)。

編輯1:如果PHP字符串和可可數據打印出相同的,他們仍然在字節級不同。 php字符串是十六進制編碼(即只包含字符0-9和a-f),而可可數據是原始字節(儘管NSData在NSLogged時NSData有用地打印出其內容的十六進制編碼字符串)。您仍然需要將第二個TRUE參數添加到php的md5()函數中以獲取原始字節字符串。

編輯2:OpenSSL的1.1.0c改變了摘要算法]在一些內部部件使用(Encryption/decryption doesn't work well between two different openssl versions)。以前使用MD5,1.1.0切換到SHA256。請注意,變更不會影響您在EVP_BytesToKeyopenssl enc之類的命令。

+0

Boaz,我的歉意......我只是重新讀這個評論,並注意到你指出了同樣的事情(PHP的md5()可以返回原始字節),我在下面評論。我的歉意......這是一個漫長的週末。 – MyztikJenz 2008-12-01 18:28:29

1

我想出了我的問題。簡短的回答:在Cocoa和PHP下使用的關鍵字長度不同。長答案...

我原來的詢問是使用Blowfish/CBC這是一個可變的密鑰長度密碼,從16字節到56.關掉Boaz的想法,認爲這個關鍵在某種程度上應該歸咎於,我轉而使用TripleDES密碼,因爲它使用24字節的固定密鑰長度。然後我發現一個問題:Cocoa/EVP_BytesToKey()返回的密鑰長度爲24個字節,但由md5()散列我的密鑰返回的值僅爲16。

問題的解決方案是讓PHP創建密鑰的方式與EVP_BytesToKey一樣,直到輸出長度至少爲止(cipherKeyLength + cipherIVLength)。下面的PHP做到了這一點(忽略任何鹽或計算迭代)

$cipher = MCRYPT_TRIPLEDES; 
$cipherMode = MCRYPT_MODE_CBC; 

$keySize = mcrypt_get_key_size($cipher, $cipherMode); 
$ivSize = mcrypt_get_iv_size($cipher, $cipherMode); 

$rawKey = "ThisIsMyKey"; 
$genKeyData = ''; 
do 
{ 
    $genKeyData = $genKeyData.md5($genKeyData.$rawKey, true); 
} while(strlen($genKeyData) < ($keySize + $ivSize)); 

$generatedKey = substr($genKeyData, 0, $keySize); 
$generatedIV = substr($genKeyData, $keySize, $ivSize); 

$output = mcrypt_decrypt($cipher, $generatedKey, $encodedData, $cipherMode, $generatedIV); 

echo "output (hex)" . bin2hex($output); 

注意,這裏將最有可能對輸出端PKCS#5填充。查看http://us3.php.net/manual/en/ref.mcrypt.php的評論pkcs5_padpkcs5_unpad用於添加和刪除所述填充。

基本上,採取密鑰的原始md5值,如果不夠長,請將密鑰追加到md5結果,並將md5附加到字符串中。清洗,沖洗,重複。 EVP_BytesToKey()的手冊頁解釋了它實際正在做什麼,並顯示瞭如果需要的話,可以放置salt值的位置。這種重新生成密鑰的方法也可以正確地重新生成初始化向量(iv),因此不必傳遞它。

但是Blowfish呢?

EVP_BytesToKey()返回密碼可能的最小密鑰,因爲它不接受密鑰大小來自的上下文。所以默認的大小是你得到的,Blowfish的大小是16字節。另一方面,mcrypt_get_key_size()返回最大可能的密鑰大小。因此,在我原來的代碼下面幾行:

$keySize = mcrypt_enc_get_key_size($td); 
$key = substr(md5("ThisIsMyKey"), 0, $keySize); 

因爲$密鑰大小設置爲56.更改上面的代碼將總是返回一個32字符鍵:

$cipher = MCRYPT_BLOWFISH; 
$cipherMode = MCRYPT_MODE_CBC; 

$keySize = 16; 

允許河豚正確解碼,但幾乎毀了可變長度密鑰的好處。總而言之,當涉及到可變密鑰長度密碼時,EVP_BytesToKey()被破壞。使用可變密鑰密碼時,您需要創建一個密鑰/ iv。我沒有深入研究它,因爲3DES可以滿足我的需求。

+0

我浪費了整整一天的時間,圍繞這個完全相同的問題進行討論,並用PHP替代EVP_BytesToKey解決了我的問題。謝謝! – grahamparks 2009-06-28 12:22:44