这是我们一直在尝试做的遗留系统翻译的第二个组成部分。我们已成功匹配 Windows ::CryptHashData 生成的初始二进制密码/密钥。
该密码/密钥被传递到::CryptDeriveKey,在那里它执行许多步骤来创建由::CryptEncrypt 使用的最终密钥。我的研究引导我找到了 CryptDeriveKey 文档,其中清楚地描述了派生 ::CryptEncrypt 密钥所需的步骤,但到目前为止我还无法让它在 PHP 端解密文件。https://learn.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-cryptderivekey https://learn.microsoft.com/en-us/windows/desktop/api/wincrypt/nf-wincrypt-cryptderivekey
根据 ::CryptDeriveKey 文档,对于我们的特定旧密钥大小,可能有一些其他未记录的步骤,这些步骤可能无法很好地理解。当前的 Windows ::CryptDeriveKey 默认设置为 ZERO SALT,这显然与 NO_SALT 有所不同。请参阅此处的盐值功能:https://learn.microsoft.com/en-us/windows/desktop/SecCrypto/salt-value-functionity https://learn.microsoft.com/en-us/windows/desktop/SecCrypto/salt-value-functionality
我们旧系统的 CryptAPI 参数如下:
提供者类型:PROV_RSA_FULL
提供者名称:MS_DEF_PROV
算法 ID CALG_RC4
RC4流加密算法说明
密钥长度:40 位。
盐长度:88 位。零盐
特别注意:但是,具有零值盐的 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;
}
以下是尝试复制文档中所述的 ::CryptDeriveKey 函数的 PHP 代码。
令 n 为所需的派生密钥长度(以字节为单位)。派生密钥是 CryptDeriveKey 完成哈希计算后哈希值的前 n 个字节。如果哈希值不是 SHA-2 系列的成员,并且所需密钥适用于 3DES 或 AES,则密钥的推导如下:
通过重复常量 0x36 64 次形成 64 字节缓冲区。令 k 为输入参数 hBaseData 表示的哈希值的长度。将缓冲区的前 k 字节设置为缓冲区的前 k 字节与输入参数 hBaseData 表示的哈希值进行异或运算的结果。
通过重复常量 0x5C 64 次形成 64 字节缓冲区。将缓冲区的前 k 字节设置为缓冲区的前 k 字节与输入参数 hBaseData 表示的哈希值进行异或运算的结果。
使用与计算 hBaseData 参数表示的哈希值相同的哈希算法对步骤 1 的结果进行哈希处理。
使用与计算 hBaseData 参数表示的哈希值相同的哈希算法对步骤 2 的结果进行哈希处理。
将步骤 3 的结果与步骤 4 的结果连接起来。
- 使用步骤 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,以便与现有部署的系统进行互操作。任何帮助或指导将不胜感激。