【问题标题】:Achieve same encryption using CryptoJS (JAVASCRIPT) and OpenSSL (PHP)使用 CryptoJS (JAVASCRIPT) 和 OpenSSL (PHP) 实现相同的加密
【发布时间】:2020-10-15 20:39:25
【问题描述】:

我想在 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 函数?到目前为止我错过了什么?

【问题讨论】:

  • 语言名称是 PHP - 全部大写。
  • PHP/openssl 使用 PKCS#7 作为填充模式,您的 Javascript 函数使用 ZeroPadding - 我相信将其中一个更改为相同模式时,您会收到相同的结果。展示一整套示例数据(密钥/apisecret、iv_message、明文、加密数据)可能是个好主意。
  • 密钥(API_SECRET)必须以CryptoJS.AES.encrypt()作为WordArray传入,目前是字符串,所以CryptoJS隐式使用了密钥推导函数。这同样适用于 IV (iv)。顺便说一句,CBC和PKCS7是默认的。
  • @ArtjomB。不幸的是,情况并非如此:const iv = CryptoJS.SHA256('a').toString(CryptoJS.enc.Hex).substring(0, 16); console.log(iv); 结果:ca978112ca1bbdca $iv = substr(hash('sha256', 'a'), 0, 16); echo $iv; 结果:ca978112ca1bbdca
  • @MichaelFehr 将填充更改为 CryptoJS.pad.Pkcs7 但仍然没有成功。

标签: javascript php encryption openssl cryptography


【解决方案1】:

要生成 PHP 代码的密文,需要对 CryptoJS 代码进行以下更改:

  • 密钥必须作为WordArray 传递。如果它作为字符串传递,它会被解释为一个passphrase,从中派生一个 32 字节的密钥。
  • PHP 用 0x00 值填充到指定长度的键太短。 CryptoJS 不这样做,并且(由于 bug)通常在密钥无效的情况下对 AES 使用未定义的整数,因此不会出现匹配的密文。
  • 在 PHP 代码中使用了 PKCS7 填充(请参阅注释)。这也必须在 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");

返回结果:

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

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

const IV_MESSAGE = "a";
const API_SECRET = "secret_key\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

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,'$1\n'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

还应考虑以下几点:

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

【讨论】:

  • 非常好的解释和完美的代码。实际上,我的真实密钥正好有 32 个字符(因此无需附加 \0 ),但了解 CrypotJS 的这种行为非常好。在将请求发送到 API 之前,我必须对 ciphertextB64 字符串进行编码,它就像一个魅力。再次感谢您的回答、代码 sn-p 和提示。问题解决了。
猜你喜欢
  • 2021-12-20
  • 1970-01-01
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-24
  • 1970-01-01
  • 2014-12-24
相关资源
最近更新 更多