Это второй компонент перевода устаревшей системы, который мы пытались сделать.Нам удалось точно сопоставить исходный двоичный пароль / ключ, который генерирует Windows :: CryptHashData.
Этот пароль / ключ передается в :: CryptDeriveKey, где он выполняет ряд шагов для создания окончательного ключа, который будет использоватьсяby :: 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/en-us/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 - необходимая длина производного ключа в байтах.Полученный ключ - это первые n байтов значения хеша после того, как CryptDeriveKey завершил вычисление хеша.Если хеш не является членом семейства SHA-2 и требуемый ключ предназначен для 3DES или AES, ключ получается следующим образом:
Формирует 64-байтовый буфер с помощьюповторяя константу 0x36 64 раза.Пусть k будет длиной значения хеша, которое представлено входным параметром hBaseData.Установите первые k байтов буфера в качестве результата операции XOR первых k байтов буфера со значением хеш-функции, которое представлено входным параметром hBaseData.
Форма a64-байтовый буфер, повторяя константу 0x5C 64 раза.Установите первые k байтов буфера в качестве результата операции XOR первых k байтов буфера со значением хеш-функции, которое представлено входным параметром hBaseData.
Хешируйте результатшага 1 с использованием того же алгоритма хеширования, который использовался для вычисления значения хеш-функции, представленного параметром hBaseData.
Хешируйте результат шага 2, используя тот же алгоритм хеширования, что икоторый используется для вычисления значения хеш-функции, представленного параметром hBaseData.
Объединить результат шага 3 с результатом шага 4.
- Использоватьпервые n байтов результата шага 5 в качестве производного ключа.
PHP-версия :: CryptDeriveKey.
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;
}
Итак, вот большие вопросы:
Кто-нибудь смог успешно реплицировать :: CryptDeriveKey с RC4 в другой системе?
Кто-нибудь знает, чего не хватает в созданном нами PHP-скрипте, который мешает ему создать тот же ключ и расшифровывает зашифрованный файл Windows CryptoAPI с помощью openssl_decrypt?
WhКак и как мы можем создать 88-битную нулевую соль, которая требуется для 40-битного ключа?
Каковы правильные параметры openssl_decrypt, которые бы приняли этот ключ и расшифровали то, что было сгенерировано :: CryptDeriveKey?
Да, мы знаем, что это небезопасно и не используется для паролей или PII.Мы хотели бы отойти от этого старого и небезопасного метода, но нам нужно сделать этот промежуточный шаг - сначала перевести оригинальное шифрование на PHP для взаимодействия с существующими развернутыми системами.Буду признателен за любую помощь или руководство.