如何在nodejs应用程序中创建相当于php代码的openssl加密解密

问题描述

我有一个PHP 上运行的应用程序,它有一些使用 openssl encrption 加密的值,使用下面的代码

<?PHP
define('OSSLENCKEY','14E2E2D1582A36172AE401CB826003C1');
define('OSSLIVKEY','747E314D23DBC624E971EE59A0BA6D28');

function encryptString($data) {
    $encrypt_method = "AES-256-CBC";
    $key = hash('sha256',OSSLENCKEY);    
    $iv = substr(hash('sha256',OSSLIVKEY),16); 
    $output = openssl_encrypt($data,$encrypt_method,$key,$iv);
    $output = base64_encode($output);
    return $output;
}

function decryptString($data){
    $encrypt_method = "AES-256-CBC";
    $key = hash('sha256',16);
    $output = openssl_decrypt(base64_decode($data),$iv);     
    return $output;
}

echo encryptString("Hello World");
echo "<br>";
echo decryptString("MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09");
?>

我有一个在 nodejs 上运行的端点,我需要根据上述 PHP 加密/解密规则解密和加密值。 我已经搜索过,但找不到解决方案。

我尝试使用图书馆 crypto 但最终出现错误 Reference

我尝试过的 nodejs 代码如下

message         = 'MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09';
const cypher    = Buffer.from(message,"base64");
const key       = crypto.createHash('sha256').update('14E2E2D1582A36172AE401CB826003C1');//.digest('hex');
// $iv          = substr(hash('sha256','747E314D23DBC624E971EE59A0BA6D28'),16);  from PHP  returns '0ed9c2aa27a31693'  need nodejs equivalent
const iv        = '0ed9c2aa27a31693'; 
const decipher  = crypto.createDecipheriv("aes-256-cbc",key,iv);  
console.log( decipher.update(contents) + decipher.final());

有人请帮我找到一个用于openssl加密和解密的nodejs代码

提前致谢

解决方法

代码中存在以下问题:

  • 密钥在 PHP 代码中以十六进制编码返回,因此在 AES-256 的 NodeJS 代码中,只需考虑密钥的前 32 个字节(PHP 会自动执行此操作)。
  • PHP 代码 Base64 隐式编码密文,因此由于显式 Base64 编码,密文被 Base64 编码两次(这是不必要的)。因此,NodeJS 代码中也需要双 Base64 编码。

另外,请注意使用静态 IV 是不安全的(但您可能只是为了测试目的而这样做)。

以下 NodeJS 代码产生与 PHP 代码相同的密文:

const crypto = require('crypto');

const plain =  'Hello World';
const hashKey = crypto.createHash('sha256');
hashKey.update('14E2E2D1582A36172AE401CB826003C1');
const key = hashKey.digest('hex').substring(0,32);
  
const hashIv = crypto.createHash('sha256');
hashIv.update('747E314D23DBC624E971EE59A0BA6D28');
const iv = hashIv.digest('hex').substring(0,16);
  
const cipher = crypto.createCipheriv('aes-256-cbc',key,iv);
var encrypted = cipher.update(plain,'utf-8','base64');
encrypted += cipher.final('base64');
encrypted = Buffer.from(encrypted,'utf-8').toString('base64');
console.log(encrypted); // MTZHaEoxb0JYV0dzNnptbEI2UXlPUT09

encrypted = Buffer.from(encrypted,'base64').toString('utf-8');
const decipher = crypto.createDecipheriv('aes-256-cbc',iv);
var decrypted = decipher.update(encrypted,'base64','utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted); // Hello World

编辑:

如注释中所述,PHP 的 hash() 方法默认以十六进制字符串形式返回哈希值(除非第三个参数明确设置为 true,参考代码中并非如此) .这使长度加倍,因为在这种编码中,散列的每个字节由两个十六进制数字 (hexits) 表示,即 2 个字节。
因此,有必要缩短 NodeJS 代码中的密钥(请参阅我原始答案的第一点)。这种缩短在 PHP 代码中不是必需的,因为 PHP 会隐式这样做(这实际上是一个设计缺陷,因为这样用户不会注意到密钥可能存在的问题)。

使用十六进制字符串有两个缺点:

  • 对于十六进制编码的字符串,每个字节由 16 个可能的值 (0-15) 组成,而不是一个字节的 256 个可能的值 (0-255)。这将安全性从 256 位降低到 128 位(在算术上等同于 AES-128),请参阅 here
  • 根据平台的不同,十六进制 a-f 可以表示为小写或大写字母,这可能会导致不同的键和 IV(对两种情况之一没有明确的约定)。

出于这些原因,使用散列的原始二进制数据而不是十六进制编码的字符串更安全、更可靠。如果要这样做,则需要进行以下更改。

在 PHP 代码中:

$key = hash('sha256',OSSLENCKEY,true);    
$iv = substr(hash('sha256',OSSLIVKEY,true),16); 

在 NodeJS 代码中:

const key = hashKey.digest();
const iv = hashIv.digest().slice(0,16)

但是请注意,此版本与旧版本不兼容,即此更改之前的加密无法在更改后解密。因此必须迁移旧数据。