将 Windows RC4 CryptDeriveKey 转换为 PHP 以用于 openssl

2024-06-25

这是我们一直在尝试做的遗留系统翻译的第二个组成部分。我们已成功匹配 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,则密钥的推导如下:

  1. 通过重复常量 0x36 64 次形成 64 字节缓冲区。令 k 为输入参数 hBaseData 表示的哈希值的长度。将缓冲区的前 k 字节设置为缓冲区的前 k 字节与输入参数 hBaseData 表示的哈希值进行异或运算的结果。

  2. 通过重复常量 0x5C 64 次形成 64 字节缓冲区。将缓冲区的前 k 字节设置为缓冲区的前 k 字节与输入参数 hBaseData 表示的哈希值进行异或运算的结果。

  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 都有专有的盐算法,并且没有办法让它们匹配。所以你应该单独腌制。有关此盐值功能的更多详细信息,请参见此处:https://learn.microsoft.com/en-us/windows/desktop/SecCrypto/salt-value-functionity https://learn.microsoft.com/en-us/windows/desktop/SecCrypto/salt-value-functionality

以下是创建供 openssl 使用的等效密钥所需的 PHP 脚本。

<?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 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://learn.microsoft.com/en-us/windows/desktop/seccrypto/example-c-program--importing-a-plaintext-key https://learn.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个字节!

使用从 BLOB 导出的键值作为下面 openssl enc 命令中的十六进制键值。

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

这将解密在 Windows 计算机上使用 CryptEncrypt 加密的文件。

正如您所看到的,当您将 CryptDeriveKey 设置为 CRYPT_NO_SALT 时,您需要的 openssl 密码或密钥就是 CryptHashData 密码的前“keylength”位。说起来很简单,但实现起来却很痛苦。祝你好运,并希望这可以帮助其他人解决遗留的 Windows 翻译问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将 Windows RC4 CryptDeriveKey 转换为 PHP 以用于 openssl 的相关文章

  • 在 imagick php 中使用 svg 原始数据生成 SVG 图像

    我正在尝试使用从 Fabric js 获取的 svg 原始数据创建 svg 图像 我使用下面的代码使用 svg 原始数据生成 svg 但它无法正常工作 public function generate svg raw svg prefix
  • 我怎样才能让我的卷发在刮擦之前等待几秒钟?

    所以我试图从网站上抓取一些数据 有一些必须被删除的名字列表 发生的情况是 如果有 15 个名字需要被删除 其中只有 5 个名字被删除 当我检查原始站点时 他们也以类似的方式加载数据 第一次加载网站时 仅显示五个名字 重新加载时会显示 10
  • whereHas 之后的 where 查询在 Laravel 5.4 中无法正常工作

    就我而言 我有channels与多对多关系的表类别 table 我想获取他们的频道类别包含像doing这样的动态值LIKE查询每个类别的标题 但我需要检查 a 的另一列channel通过 where 子句 这是一个channel结构 id
  • PHP 如果找到特定值范围,则从主数组中删除子数组

    假设我有以下数组 9 gt Array 0 gt Bob Smith 1 gt email protected cdn cgi l email protection 2 gt Helsinki 3 gt 10 4 gt 34 5 gt 20
  • PHP“按引用分配”的奇怪现象

    我发现了一个代码片段 其中包括 a b 但没有测试 b 是否确实存在 if isset b 我不确定 PHP 是如何处理这个问题的 所以我进行了一个快速的裸测试 现在我更加感兴趣了 a array a gt b x gt y b array
  • mysql数据库的“零知识”加密

    我一直在研究 Web 应用程序数据库层的加密 它使用MySQL 5 1 或更高版本 我记不清了 该应用程序由我的组织管理 为公共客户存储数据 最简单的选择是 AES ENCRYPT AES DECRYPT 如果坏人以某种方式访问 我的数据库
  • 如果 Woocommerce 单一产品中的库存数量少于 10,请在库存数量旁边添加文本

    我发现这是作为另一个线程的评论发布的 但我不知道应该用它做什么 if product gt get stock quantity lt 10 echo Limited supply left 我想它应该有一个钩子 我试过woocommerc
  • 如何在PHP5.5上安装V8js?

    我想在 Ubuntu 12 04 上安装 PHP5 5 的 v8js 扩展 但无法使其工作 当我尝试使用 PECL 安装 v8js 扩展版本 0 2 0 最新 时 我收到以下消息 配置 错误 libv8 必须是版本 3 24 6 或更高版本
  • PHP 的 Checkstyle 作为 Eclipse 插件

    有这样的插件可用吗 是的 有一个 PHP 工具集成 http www phpsrc org 与 Checkstyle 等效的是 PHP CodeSniffer 一个 pear 包 您可以在这里找到更多信息PHP CodeSniffer ht
  • 计算哪些字符串将具有相同的哈希值

    使用 SHA 1 是否可以计算出哪些有限字符串将呈现相等的哈希值 您正在寻找的是该问题的解决方案碰撞问题 http en wikipedia org wiki Collision 28computer science 29 也可以看看碰撞攻
  • Magento - 将媒体库属性添加到产品平板中

    如何将 media gallery 属性类型添加到产品平面表中 以便可以在产品列表中使用它 可以通过将 用于产品列表 定义为 是 来添加它们 完成此操作后 您将需要重新索引产品平面 编辑 请参阅解决方案以获取最终答案 Source 我可以向
  • Codeigniter 未连接到 SQL Server

    我正在尝试使用 CodeIgniter 连接到 SQL 服务器 如果我使用 sqlsrv 驱动程序 我会收到致命错误消息 如果我使用 odbc 驱动程序 我会收到 无法使用提供的设置连接到数据库服务器 错误消息 有谁知道如何解决这个问题 我
  • Laravel 使用 Monolog\Handler\BrowserConsoleHandler 进行日志记录

    How can 拉拉维尔 5的日志记录更改为Monolog Handler BrowserConsoleHandler What doesn t在 Laravel 5 中工作但是does在独立的 PHP 文件中工作 use Illumina
  • Laravel Factory:手动增量列

    对于以下工厂定义 该列order需要按顺序进行 已经有专栏了id即自动递增 第一行的order应该开始于1以及每个附加行的order应该是下一个数字 1 2 3 etc factory gt define App AliasCommand
  • POST 数据加密 - HTTPS 足够吗?

    考虑一个场景 其中用户身份验证 用户名和密码 由用户在页面的表单元素中输入 然后提交 POST 数据通过 HTTPS 发送到新页面 其中 php 代码将检查凭据 现在 如果黑客位于网络中 并说可以访问所有流量 那么在这种情况下应用程序层安全
  • 更新 Composer 依赖项时 Artisan 命令出错

    我正在为 Laravel 开发一个库 其中包含一个服务提供者 我已将此库添加到另一个项目的composer json file The composer json 主项目 的文件包含以下脚本 scripts post root packag
  • 尝试加载 php_oci8.dll 时 PHP 启动时出现警告

    我正在使用 XAMPP 并尝试为 sql 配置 Oracle 连接 我取消了该行的注释extension php oci8 dll一开始出现错误 缺少oci dll 但后来我从Oracle网页下载了instantclient 我尝试过版本
  • 在 MySQL 数据库中存储大文件的更好方法?

    我有一个 PHP 脚本 您可以使用它上传非常大的文件 最大 500MB 并且该文件的内容存储在 MySQL 数据库中 目前我做这样的事情 mysql query INSERT INTO table VALUES uploadedfile f
  • 如何获得DiVs等级?

    html div class div class p strong span style color FF0000 Content1 span strong p p style text align center Content2 img
  • php SimpleXML 属性丢失

    我这里有以下 xml 文档 编辑 示例见下文 我正在使用 php SimpleXML 将其转换为对象来读取它 xmlContent file get contents path test xml tablesRaw new SimpleXM

随机推荐