簡體   English   中英

將Windows RC4 CryptDeriveKey轉換為openssl的PHP

[英]Translate Windows RC4 CryptDeriveKey to PHP for openssl

這是我們一直在嘗試的遺留系統轉換的第二部分。 我們已經設法完全匹配Windows :: CryptHashData生成的初始二進制密碼/密鑰。

該密碼/密鑰將傳遞給:: CryptDeriveKey,在此執行許多步驟來創建最終的密鑰,以供:: CryptEncrypt使用。 我的研究使我進入了CryptDeriveKey文檔,該文檔清楚地描述了為:: CryptEncrypt導出密鑰所需的步驟,但到目前為止,我還無法在PHP方面獲得它來解密文件。 https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-cryptderivekey

根據:: CryptDeriveKey文檔,對於我們特定的傳統密鑰大小,可能還有一些其他未記錄的步驟,可能無法很好地理解。 默認情況下,當前Windows :: CryptDeriveKey設置為ZERO SALT,這顯然與NO_SALT有所不同。 在此處查看鹽值功能: https : //docs.microsoft.com/zh-cn/windows/desktop/SecCrypto/salt-value-functionality

我們的舊系統的CryptAPI上的參數如下:

提供程序類型:PROV_RSA_FULL

提供程序名稱:MS_DEF_PROV

算法ID CALG_RC4

說明RC4流加密算法

密鑰長度:40位。

鹽長:88位。 ZERO_SALT

特別說明:然而,帶有零值鹽的40位對稱密鑰並不等同於沒有鹽的40位對稱密鑰。 為了實現互操作性,必須在不添加鹽的情況下創建密鑰。 此問題是由僅使用正好40位的密鑰的默認情況導致的。

我不希望導出密鑰,而是重現創建最終加密密鑰的過程,該過程將最終的加密密鑰傳遞給:: CryptEncrypt以用於RC4加密算法,並使其與openssl_decrypt一起使用。

這是當前適用於加密的Windows代碼。

try {
    BOOL bSuccess;
    bSuccess = ::CryptAcquireContextA(&hCryptProv, 
                                      CE_CRYPTCONTEXT, 
                                      MS_DEF_PROV_A, 
                                      PROV_RSA_FULL, 
                                      CRYPT_MACHINE_KEYSET);

    ::CryptCreateHash(hCryptProv, 
                      CALG_MD5, 
                      0, 
                      0, 
                      &hSaveHash);

    ::CryptHashData(hSaveHash, 
                    baKeyRandom, 
                    (DWORD)sizeof(baKeyRandom), 
                    0);

    ::CryptHashData(hSaveHash, 
                    (LPBYTE)T2CW(pszSecret), 
                    (DWORD)_tcslen(pszSecret) * sizeof(WCHAR), 
                     0);

    ::CryptDeriveKey(hCryptProv, 
                     CALG_RC4, 
                     hSaveHash, 
                     0, 
                     &hCryptKey);

    // Now Encrypt the value
    BYTE * pData = NULL;
    DWORD dwSize = (DWORD)_tcslen(pszToEncrypt) * sizeof(WCHAR); 
    // will be a wide str
    DWORD dwReqdSize = dwSize;

    ::CryptEncrypt(hCryptKey, 
                   NULL, 
                   TRUE, 
                   0, 
                   (LPBYTE)NULL, 
                   &dwReqdSize, 0);

    dwReqdSize = max(dwReqdSize, dwSize);

    pData = new BYTE[dwReqdSize];

    memcpy(pData, T2CW(pszToEncrypt), dwSize);

    if (!::CryptEncrypt(hCryptKey, 
                        NULL, 
                        TRUE, 
                        0, 
                        pData, 
                        &dwSize, 
                        dwReqdSize)) {

            printf("%l\n", hCryptKey);
            printf("error during CryptEncrypt\n");
            }

    if (*pbstrEncrypted)
    ::SysFreeString(*pbstrEncrypted);
    *pbstrEncrypted = ::SysAllocStringByteLen((LPCSTR)pData, dwSize);
    delete[] pData;
    hr = S_OK;
}

這是PHP文檔,試圖按照文檔中的描述復制:: CryptDeriveKey函數。

令n為所需的派生密鑰長度(以字節為單位)。 派生密鑰是CryptDeriveKey完成哈希計算之后的哈希值的前n個字節。 如果哈希不是SHA-2家族的成員,並且所需的密鑰是3DES或AES,則密鑰的導出如下:

  1. 通過重復常數0x36 64次來形成64字節的緩沖區。 令k為由輸入參數hBaseData表示的哈希值的長度。 將緩沖區的前k個字節設置為使用輸入參數hBaseData表示的哈希值對緩沖區的前k個字節進行XOR操作的結果。

  2. 通過重復常數0x5C 64次形成一個64字節的緩沖區。 將緩沖區的前k個字節設置為緩沖區的前k個字節與由輸入參數hBaseData表示的哈希值進行XOR運算的結果。

  3. 通過使用與用於計算由hBaseData參數表示的哈希值的哈希算法相同的哈希算法來哈希步驟1的結果。

  4. 通過使用與用於計算由hBaseData參數表示的哈希值的哈希算法相同的哈希算法來哈希步驟2的結果。

  5. 將步驟3的結果與步驟4的結果連接起來。

  6. 使用步驟5的結果的前n個字節作為派生密鑰。

:: CryptDeriveKey的PHP版本。

function cryptoDeriveKey($key){

    //Put the hash key into an array
    $hashKey1 = str_split($key,2);
    $count = count($hashKey1);
    $hashKeyInt = array();

    for ($i=0; $i<$count; $i++){
        $hashKeyInt[$i] = hexdec($hashKey1[$i]);
    }
    $hashKey = $hashKeyInt;

    //Let n be the required derived key length, in bytes.  CALG_RC4 = 40 bits key or 88 salt bytes
    $n = 40/8;

    //Let k be the length of the hash value that is represented by the input parameter hBaseData
    $k = 16;

    //Step 1 Form a 64-byte buffer by repeating the constant 0x36 64 times   
    $arraya = array_fill(0, 64, 0x36);

    //Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value 
    for ($i=0; $i<$k; $i++){
        $arraya[$i] = $arraya[$i] ^ $hashKey[$i];
    }

    //Hash the result of step 1 by using the same hash algorithm as hBaseData
    $arrayPacka = pack('c*', ...$arraya);
    $hashArraya = md5($arrayPacka);

    //Put the hash string back into the array
    $hashKeyArraya = str_split($hashArraya,2);
    $count = count($hashKeyArraya);
    $hashKeyInta = array();
    for ($i=0; $i<$count; $i++){
        $hashKeyInta[$i] = hexdec($hashKeyArraya[$i]);
    }

    //Step 2 Form a 64-byte buffer by repeating the constant 0x5C 64 times. 
    $arrayb = array_fill(0, 64, 0x5C);

    //Set the first k bytes of the buffer to the result of an XOR operation of the first k bytes of the buffer with the hash value
    for ($i=0; $i<$k; $i++){
        $arrayb[$i] =  $arrayb[$i] ^ $hashKey[$i];
    }

    //Hash the result of step 2 by using the same hash algorithm as hBaseData    
    $arrayPackb = pack('c*', ...$arrayb);
    $hashArrayb = md5($arrayPackb);

    //Put the hash string back into the array
    $hashKeyArrayb = str_split($hashArrayb,2);
    $count = count($hashKeyArrayb);
    $hashKeyIntb = array();
    for ($i=0; $i<$count; $i++){
        $hashKeyIntb[$i] = hexdec($hashKeyArrayb[$i]);
    }

    //Concatenate the result of step 3 with the result of step 4.
    $combined = array_merge($hashKeyInta, $hashKeyIntb);

    //Use the first n bytes of the result of step 5 as the derived key.
    $finalKey = array();
    for ($i=0; $i <$n; $i++){
        $finalKey[$i] =  $combined[$i];
    }
    $key = $finalKey;

    return $key;
}

PHP解密功能

function decryptRC4($encrypted, $key){
    $opts = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
    $cypher = ‘rc4-40’;
    $decrypted = openssl_decrypt($encrypted, $cypher, $key, $opts);
    return $decrypted; 
}

因此,這里有個大問題:

有沒有人能夠在另一個系統上使用RC4成功復制:: CryptDeriveKey?

有誰知道我們創建的PHP腳本中缺少哪些內容,從而阻止該腳本創建相同的密鑰並使用openssl_decrypt解密Windows CryptoAPI加密文件?

我們在哪里以及如何創建40位密鑰所需的88位零鹽?

可以接受此密鑰並解密:: CryptDeriveKey生成的內容的正確的openssl_decrypt參數是什么?

是的,我們知道這是不安全的,並且不用於密碼或PII。 我們希望擺脫這種古老而又不安全的方法,但是我們需要采取此過渡步驟,首先將原始加密轉換為PHP,以實現與現有已部署系統的互操作性。 任何幫助或指導,將不勝感激。

以防萬一其他人走這條路,這里是上述所有問題的答案。

您可以使用openssl在PHP上復制:: CryptDeriveKey,但必須首先在Windows端滿足一些先決條件。

必須將CryptDeriveKey設置為CRYPT_NO_SALT,如下所示:

::CrypeDeriveKey(hCryptProv, CALG_RC4, hSaveHash, CRYPT_NO_SALT, &hCryptKey)

這將允許您從哈希中創建密鑰,並在PHP中生成匹配密鑰,該密鑰可在openssl上使用。 如果您未設置任何鹽參數,則將獲得由未知專有鹽算法創建的密鑰,該算法無法在另一個系統上匹配。

您必須設置CRYPT_NO_SALT的原因是因為CryptAPI和openssl都擁有專有的salt算法,並且無法使它們匹配。 因此,您應該分開腌制。 這里有關於此鹽值功能的更多詳細信息: https : //docs.microsoft.com/zh-cn/windows/desktop/SecCrypto/salt-value-functionality

這是PHP腳本需要創建一個等效密碼以供openssl使用的樣子。

<?php
$random = pack('c*', 87,194,...........);
$origSecret = 'ASCII STRING OF CHARACTERS AS PASSWORD'; 

//Need conversion to match format of Windows CString or wchar_t*
//Windows will probably be UTF-16LE and LAMP will be UTF-8
$secret = iconv('UTF-8','UTF-16LE', $origSecret);

//Create hash key from Random and Secret
//This is basically a hash and salt process.
$hash = hash_init("md5");
hash_update($hash, $random);
hash_update($hash, $secret);
$key = hash_final($hash);

$key = cryptoDeriveKey($key);
//Convert the key hex array to a hex string for openssl_decrypt
$count = count($key);
$maxchars = 2;
for ($i=0; $i<$count; $i++){
    $key .= str_pad(dechex($key[$i]), $maxchars, "0", STR_PAD_LEFT);
}

重要說明:OpenSSL期望密鑰是從哈希派生的原始十六進制值,不幸的是,openssl_decrypt()希望與字符串或密碼具有相同的值。 因此,此時您必須進行十六進制到字符串的轉換。 這里有很多關於為什么必須這樣做的文章。 http://php.net/manual/en/function.openssl-encrypt.php

$opts = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
//Convert key hex string to a string for openssl_decrypt
//Leave it as it is for openssl command line.
$key = hexToStr($key);
$cipher = 'rc4-40';
$encrypted = “the data you want to encrypt or decrypt”;
$decrypted = openssl_decrypt($encrypted, $cipher, $key, $opts);  

echo $decrypted;  //This is the final information you’re looking for


function cryptoDeriveKey($key){
//convert the key into hex byte array as int
    $hashKey1 = str_split($key,2);
    $count = count($hashKey1);
    $hashKeyInt = array();
    for ($i=0; $i<$count; $i++){
        $hashKeyInt[$i] = hexdec($hashKey1[$i]);
    }
    $hashKey = $hashKeyInt;
    //Let n be the required derived key length, in bytes.  CALG_RC4 = 40 bits key with 88 salt bits
    $n = 40/8;
    //Chop the key down to the first 40 bits or 5 bytes.
    $finalKey = array();
    for ($i=0; $i <$n; $i++){
        $finalKey[$i] =  $hashKey[$i];
    }
    return $finalKey;
}


function hexToStr($hex){
    $string='';
    for ($i=0; $i < strlen($hex)-1; $i+=2){
        $string .= chr(hexdec($hex[$i].$hex[$i+1]));
    }
return $string;
}
?>

如果在使用上述代碼后無法正確獲取值,可以嘗試從CryptoAPI導出密鑰值並使用openssl命令行對其進行測試。

首先,您必須設置CryptDeriveKey以允許使用CRYPT_EXPORTABLE和CRYPT_NO_SALT導出密鑰

::CrypeDeriveKey(hCryptProv, CALG_RC4, hSaveHash, CRYPT_EXPORTABLE | CRYPT_NO_SALT, &hCryptKey)

如果您想知道如何從導出的鍵中顯示PLAINTEXTKEYBLOB,請單擊此鏈接。 https://docs.microsoft.com/en-us/windows/desktop/seccrypto/example-c-program--importing-a-plaintext-key

這是導出密鑰Blob的示例0x08 0x02 0x00 0x00 0x01 0x68 0x00 0x00 0x05 0x00 0x00 0x00 0xAA 0xBB 0xCC 0xDD 0xEE

0x08 0x02 0x00 0x00 0x01 0x68 0x00 0x00 // BLOB標頭幾乎完全匹配0x05 0x00 0x00 0x00 //以字節為單位的密鑰長度正確5字節0xAA 0xBB 0xCC 0xDD 0xEE //我們創建的哈希密鑰的前5個字節!

在下面的openssl enc命令中,使用從BLOB導出的鍵值作為十六進制鍵值。

openssl enc -d -rc4-40 -in testFile-NO_SALT-enc.txt -out testFile-NO_SALT-dec.txt -K "Hex Key Value" -nosalt -nopad

這將解密使用CryptEncrypt在Windows計算機上加密的文件。

如您所見,將CryptDeriveKey設置為CRYPT_NO_SALT時,所需的openssl密碼或密鑰是CryptHashData密碼的第一個“密鑰長度”位。 說的很簡單,但是很難理解。 祝您好運,並希望這對舊版Windows翻譯問題有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM