Nodejs如何实现OpenSSL AES-CBC加密(来自PHP)?

ioekq8ef  于 2023-02-21  发布在  PHP
关注(0)|答案(1)|浏览(310)

我目前正在将一个加密算法从PHP翻译成Typescript,以便在一个非常特殊的API中使用,该API要求发布的数据使用API密钥和Secret进行加密。下面是提供的示例,说明如何在PHP中正确地加密数据,以便与API一起使用(实现密钥和IV的方式不能更改):

$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);

$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);

return $completedEncryption;
  • 在上面的代码中,* base64Url_Encode * 函数所做的唯一事情就是将二进制数据转换为有效的Base64URL字符串 *。

现在是我在Typescript中实现的代码:

import { createHash, createCipheriv } from 'node:crypto'

const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)

// Generate key
/* 
  Because the OpenSSL function in PHP automatically pads the string with /null chars,
  do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key, 
  instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)

// Create Cipher 
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
    
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');

// Return base64URL string
return Buffer.from(encrypted).toString('base64url');

上面的Typescript代码和前面的PHP代码给予相同的输出。我已经研究了原始的OpenSSL代码,我已确保填充算法匹配(pcks5pcks7)并检查每个输入Buffer是否与PHP中的输入具有相同的字节长度。我目前在想,是否是某种二进制畸形导致数据在Javascript中发生变化?
我希望有Maven能帮我解答这个问题。也许我忽略了什么。先谢了。

qnakjoqk

qnakjoqk1#

PHP中的md5函数比较愚蠢,它默认为十六进制输出而不是二进制输出:

md5(string $string, bool $binary = false): string

这也是为什么代码不会抱怨密钥(由MD5散列构造而成)太小的原因,它在ASCII或UTF8编码后输入32字节,而不是AES-128使用的16字节。
显然它使用的是小写编码,尽管甚至没有指定。您也可以指定NodeJS的编码,请参阅digest方法的文档。它似乎也使用了小写编码,尽管我也无法直接找到编码的确切规范。
一旦你完成了你的任务,请尽快删除代码,因为你永远不应该从密钥计算IV;密钥和IV组合应该是唯一的,因此如果密钥被重用,则上述代码不是IND-CPA安全的。
如果你想知道为什么它是如此愚蠢:MD5的输出已经在标准中有了规定,是二进制的,而且从函数中不可能看到它在做什么,你必须查找代码,如果你在做比较,它也会很糟糕;即使你在比较字符串,那么使用大写而不是小写是很容易的(两者都同样有效,大写十六进制实际上更容易为人类阅读,因为我们出于某种原因更多地关注字母的顶部)。
基本上,它采取了最小意外的原则,并把它扔到了窗口之外。输出的编码可以做得最优,NodeJS实现正确地做到了这一点。

相关问题