通过阅读本文,你将了解 OpenSSL 中的 EVP_BytesToKey 和 CryptoJS.AES 中加解密函数 encrypt 和 decrypt 是如何实现的。

AES 算法(Rijndael 算法)在加解密时需要提供密钥(key)和初始向量(iv),而用 CryptoJS.AES 库在加解密时却需要提供密码(passphrase 或者 password):

var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");​
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");

在经过一番探索之后,我发现 CryptoJS.AES 库加解密本质并没有改变,只不过为了使用方便,CryptoJS.AES 库使用 OpenSSL 的 EVP_BytesToKey 函数将 passphrase 转换成 32 位 key 和 16 位 iv 以供 AES 算法使用。

要实现这个 CryptoJS.AES 库加解,首先要实现 EVP_BytesToKey 函数。找了蛮多实现,发现 browserify/EVP_BytesToKey 比较容易阅读,接下来我就用列表的形式展示这个函数的实现过程:

  1. 将 password 转换成二进制 buffer
  2. 将 salt 转换成二进制 buffer,并确保 salt 是 8 位
  3. 初始化 key、iv、tmp 等二进制 buffer
  4. 进入 md5 加密循环:

    • 使用 md5 算法,依次传入 tmp、password 和 salt,计算得到新的 tmp
    • 使用 tmp 依次填充 key 和 iv
    • 如果两者没被填满,则继续循环,否则跳出循环
  5. 返回 key 和 iv

注意,这个实现就算不加入 salt 依然得到 key 和 iv,一般来说,我们用的都是加 salt 的,这个 salt 在加密时也可能是随机生成的,会在最后的加密字符串中体现。有 salt 参与的 AES 加密得到的加密字符串以 base64 编码储存,其所对应的字节数组由三部分组成:

  1. Salted__ 这八个字符对应的 ASCII 编码,占 8 个字节
  2. 加密时用到的随机种子,占 8 个字节
  3. 用 AES 算法(Rijndael 算法,CBC 模式,256 位密钥,128 位块)加密得到的字节数组

好了,读到这里,想必你也可以自己动手实现这个函数了。感兴趣的话可以参考我写的 Rust 实现代码 felixmaker/crypto_aes

最后修改:2024 年 03 月 02 日
如果觉得我的文章对你有用,请随意赞赏