AES-256-GCM在PHP与TypeScript Crypto中结果不一致的解决求助
问题
我尝试将PHP中基于AES-256-GCM的加密逻辑迁移至Node.js(TypeScript),但两者生成的加密结果存在差异,具体如下:
PHP示例代码
<?php function Encrypt(string $data = "", string $merKey = "", string $merIV = "") { $tag = ""; // default empty $encrypted = openssl_encrypt($data, "aes-256-gcm", trim($merKey), 0, trim($merIV), $tag); return trim(bin2hex($encrypted . ":::" . base64_encode($tag))); } // For example $data = "exampleData"; // Secret $merKey = "12345678901234567890123456789012"; $merIV = "1234567890123456"; $encryptStr = Encrypt($data, $merKey, $merIV); print_r($encryptStr); // 输出:4d38727078724f39655347563066513d3a3a3a5a46415a536c374a35326c36513344655833754768413d3d ?>
Node.js(TypeScript)示例代码
import { createCipheriv } from "crypto"; let authTag: Buffer; class SymmetricEncryptor { private readonly algorithm = "aes-256-gcm"; private readonly secretKey: Buffer; private readonly iv: Buffer; public constructor(strKey: string, strIV: string) { this.secretKey = Buffer.from(strKey); this.iv = Buffer.from(strIV); } public encrypt(original: Buffer): Buffer { const cipher = createCipheriv(this.algorithm, this.secretKey, this.iv); let cipherText = cipher.update(original); cipherText = Buffer.concat([cipherText, cipher.final()]); authTag = cipher.getAuthTag(); return Buffer.concat([ cipherText, Buffer.from(":::"), Buffer.from(authTag.toString("base64")) ]); } } const merKey = "12345678901234567890123456789012"; const merIV = "1234567890123456"; const symmetricEncryptor = new SymmetricEncryptor(merKey, merIV); let data = "exampleData"; const encryptedData = symmetricEncryptor.encrypt( Buffer.from(data, "utf-8") ); console.log(encryptedData.toString('hex')); // 输出:33cae9c6b3bd792195d1f43a3a3a5a46415a536c374a35326c36513344655833754768413d3d
其中:::分隔符后的编码数据是正确的,但分隔符前的内容存在差异。我推测问题可能出在TypeScript的填充设置上,但不知如何修复;我尝试修改PHP中openssl_encrypt的options参数从0改为3,结果仍一致,但无法修改PHP的逻辑。请问如何修改上述Node.js代码以生成与PHP相同的加密结果?
解决方案
问题核心在于PHP的openssl_encrypt默认会把加密后的原始二进制数据做Base64编码,而当前Node.js代码直接使用了加密后的原始二进制字节,没有做Base64编码,这就导致了分隔符前的内容差异。
修改后的Node.js(TypeScript)代码如下:
import { createCipheriv } from "crypto"; let authTag: Buffer; class SymmetricEncryptor { private readonly algorithm = "aes-256-gcm"; private readonly secretKey: Buffer; private readonly iv: Buffer; public constructor(strKey: string, strIV: string) { this.secretKey = Buffer.from(strKey); this.iv = Buffer.from(strIV); } public encrypt(original: Buffer): Buffer { const cipher = createCipheriv(this.algorithm, this.secretKey, this.iv); let cipherText = cipher.update(original); cipherText = Buffer.concat([cipherText, cipher.final()]); authTag = cipher.getAuthTag(); // 关键改动:将加密后的原始二进制数据做Base64编码,再转成Buffer const base64CipherText = Buffer.from(cipherText.toString('base64')); return Buffer.concat([ base64CipherText, Buffer.from(":::"), Buffer.from(authTag.toString("base64")) ]); } } const merKey = "12345678901234567890123456789012"; const merIV = "1234567890123456"; const symmetricEncryptor = new SymmetricEncryptor(merKey, merIV); let data = "exampleData"; const encryptedData = symmetricEncryptor.encrypt( Buffer.from(data, "utf-8") ); console.log(encryptedData.toString('hex')); // 现在输出会和PHP一致:4d38727078724f39655347563066513d3a3a3a5a46415a536c374a35326c36513344655833754768413d3d
改动说明
- PHP的
openssl_encrypt函数在默认选项(options=0)下,返回的是Base64编码后的密文,而非原始二进制密文。 - 原Node.js代码直接使用了
cipher.update()和cipher.final()得到的原始二进制数据,没有做Base64编码,导致两者密文部分的编码方式不一致。 - 修改后,先将原始二进制密文转成Base64字符串,再转成Buffer,这样拼接后的内容就和PHP的
$encrypted部分完全一致了。
内容的提问来源于stack exchange,提问作者James Fu
相关产品推荐
相关产品推荐

