使用 CryptoJS (JAVASCRIPT) 和 OpenSSL (PHP) 实现相同的加密

Achieve same encryption using CryptoJS (JAVASCRIPT) and OpenSSL (PHP)

我想在 ReactJS 应用程序中实现 PhP 加密功能。我需要以使用 OpenSSL 库函数 (openssl_encrypt) 创建的特定格式发送令牌。

与JAVASCRIPT 函数相比,PHP 函数生成的字符串短几个字符。当然,两者都获得相同的属性和特性。

PHP:

protected static function encrypt($stringData) {
  $encrypted = false;
  $encrypt_method = 'AES-256-CBC';
  $iv = substr(hash('sha256', static::$ivMessage), 0, 16);
  $encrypted= openssl_encrypt($stringData, $encrypt_method, static::$apiSecret, 0, $iv);

  return $encrypted;
}

JAVASCRIPT:

export const encrypt = (stringData) => {
  const iv = CryptoJS.SHA256(IV_MESSAGE).toString(CryptoJS.enc.Hex).substring(0, 16);
  const encrypted = CryptoJS.AES.encrypt(stringData, API_SECRET, {
    iv,
    mode: CryptoJS.mode.CBC,
    pad: CryptoJS.pad.ZeroPadding,
  });

  return encrypted;
};

示例常量:

const stringData = "{"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"test@test.hu","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true}";
const IV_MESSAGE = "a";
const API_SECRET = "secret_key"; 

(与 PHP 函数相同 --> $stringData, $ivMessage; $apiSecret)

如何实现在JAVASCRIPT中“复制”PHP函数?到目前为止我错过了什么?

CryptoJS 代码中的以下更改对于生成 PHP 代码的密文是必要的:

  • 密钥必须作为 WordArray 传递。如果它作为字符串传递,它被解释为 passphrase,从中派生出 32 字节的密钥。
  • PHP 用 0x00 值填充太短的键,直到指定长度。 CryptoJS 不会这样做,并且(由于 bug)通常在无效密钥的情况下为 AES 使用未定义的整数,因此预计不会有匹配的密文。
  • PKCS7 填充用于 PHP 代码(见注释)。这也必须应用于 CryptoJS 代码,但是它是 default(以及 CBC 模式)。

以下PHP代码:

function encrypt($stringData) {
    
  $ivMessage = "a";
  $apiSecret = "secret_key"; 

  $encrypted = false;
  $encrypt_method = 'AES-256-CBC';
  $iv = substr(hash('sha256', $ivMessage), 0, 16);
  $encrypted= openssl_encrypt($stringData, $encrypt_method, $apiSecret, 0, $iv);

  return $encrypted;
}

$stringData = '{"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"test@test.hu","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true}';
print(encrypt($stringData) . "\n");

returns结果:

d/H+FfTaT/3tIkaXtIix937p6Df/vlnxagNJGJ7ljj48phT7oA7QssTatL3WNZY0Igt0r5ObGyCt0AR0IccVTFVZdR+nzNe+RmKQEoD4dj0mRkZ7qi/y3bAICRpFkP3Nz42fuILKApRtmZqGLTNO6dwlCbUVvjg59fgh0wCzy15g51G6CYLsEHa89Dt193g4qcXRWFgI9gyY1Gq7FX0G6Ers0fySQjjNcfDJg0Hj5aSxbPU6EPn14eaWqkliNYSMqzKhe0Ev7Y54x2YlUCNQeLZhwWRM2W0N+jGU7W+P/bCtF4Udwv4cweUESXkHLGtlQ0K6O5etVJDtb7ZtdEI/sA==

下面的 CryptoJS 代码生成相同密文:

const IV_MESSAGE = "a";
const API_SECRET = "secret_key[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]";

function encrypt(stringData){
    const iv = CryptoJS.SHA256(IV_MESSAGE).toString(CryptoJS.enc.Hex).substring(0, 16);
    const encrypted = CryptoJS.AES.encrypt(
        stringData, 
        CryptoJS.enc.Utf8.parse(API_SECRET), 
        {
            iv: CryptoJS.enc.Utf8.parse(iv)
        });

    return encrypted;
};

const stringData = {"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"test@test.hu","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true};
const ciphertextB64 = encrypt(JSON.stringify(stringData)).toString();

console.log(ciphertextB64.replace(/(.{64})/g,'\n'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

还应考虑以下因素:

  • 生成IV时避免将IV编码为十六进制字符串,直接使用二进制数据更可靠。否则,您还必须记住,根据平台的不同,通常可以应用十六进制数字的不同 upper/lower 大小写。这里这并不重要,因为在这两种情况下都使用小写字母。
  • 如果你真的应该应用像 secret_key 这样的密码作为密钥,你还应该使用合理的密钥派生函数(例如 PBKDF2 结合随机生成的盐) 因为低熵。 CryptoJS 中使用的默认 KDF,即专有的 OpenSSL 函数 EVP_BytesToKey,不应应用,因为它不是标准并且也被认为相对不安全。
  • 出于安全原因,不能使用静态 IV。相反,应为每次加密应用随机生成的 IV。 IV 不是秘密的,通常以 IV 的顺序与密文连接,密文(见评论)。