为了账号安全,请及时绑定邮箱和手机立即绑定

使用密钥和 IV 从节点解密 AES 到 Golang Panic

使用密钥和 IV 从节点解密 AES 到 Golang Panic

Go
波斯汪 2022-10-24 15:27:40
我在 node.js 中有以下代码,使用 crypto-js 使用带有密钥和 IV 的 AES 加密密码。const crypto = require('crypto-js');const cryptKey = 'b676eac8cf70442385dfd4bcfaa61b52';const createRandomIv = function () {    const keySize = 192 / 32;    const ivSize = 128 / 32;    const evp = crypto.algo.EvpKDF.create({ keySize: keySize + ivSize, hasher: crypto.algo.SHA1 }).compute(cryptKey);    const iv = crypto.lib.WordArray.create(evp.words.slice(keySize), ivSize * 4);    return iv.toString();};const encryptPassword = function (password) {    const iv = createRandomIv();    const hash = crypto.AES.encrypt(        password,        cryptKey, {            iv,            mode: crypto.mode.CTR        }    );    const base64 = crypto.enc.Base64.parse(hash.toString());    const eHex = base64.toString(crypto.enc.Hex);    return `${iv}:${eHex}`;};const decryptPassword = function (encryptedPwd) {    const split = encryptedPwd.split(':');    if (split.length < 2) return '';    const reb64 = crypto.enc.Hex.parse(split[1]);    const bytes = reb64.toString(crypto.enc.Base64);    const hash = crypto.AES.decrypt(bytes, cryptKey, {        iv: split[0],        mode: crypto.mode.CTR    });    const plain = hash.toString(crypto.enc.Utf8);    return plain;};这是来自节点 js 的加密密码。const encryptedPassword = encryptPassword("Stack Overflow");console.log(encryptedPassword);// 2db5c01b4825b6d4dd7a7b96f04f3bb5:53616c7465645f5f691671363cda1b9d05ee6bdd637e1e99bc3b29ef2ad7ec53并且已经尝试使用 golang 对其进行解密,如下所示package mainimport (    "crypto/aes"    "crypto/cipher"    "fmt"    "strings")}但它恐慌如下。恐慌:cipher.NewCBCDecrypter:IV 长度必须等于块大小 goroutine 1 [运行]:crypto/cipher.NewCBCDecrypter({0x10c4ee8, 0xc000066060}, {0xc00001e040, 0x1, 0x20})
查看完整描述

1 回答

?
qq_笑_17

TA贡献1818条经验 获得超7个赞

在 CryptoJS 代码中,第二个参数 incrypto.AES.encrypt()作为字符串传递,因此它被解释为密码短语。

因此,在加密期间,首先创建一个 8 字节的 salt,然后使用 KDF 导出密码短语、密钥和 IV EVP_BytesToKey()

createRandomIv()使用并显式传入的 IV将crypto.AES.encrypt()被忽略!

hash.ToString() 以 OpenSSL 格式返回结果,该格式由前缀Salted__后跟 salt 和实际密文组成,均采用 Base64 编码。eHex包含相同的数据,但十六进制而不是 Base64 编码。

CryptoJS 不会自动禁用 CTR 等流密码模式的填充,因此使用 PKCS#7 填充数据,尽管这对于 CTR 不是必需的。


在 Go 代码中,必须首先删除不需要的 IV。从剩余的数据中,确定盐和密文。

从 salt 和 passphrase 中,可以使用 检索密钥和 IV evp.BytesToKeyAES256CBCMD5()

使用密钥和 IV 可以使用 AES-CTR 进行解密。

最后,必须删除 PKCS#7 填充。

下面的 Go 代码实现了这些步骤。输入数据是使用 NodeJS 代码生成的:

import (

    "crypto/aes"

    "crypto/cipher"

    "encoding/hex"

    "fmt"

    "strings"


    "github.com/walkert/go-evp"

)


func main() {


    // Determine salt and actual ciphertext

    encryptedPwd := "2db5c01b4825b6d4dd7a7b96f04f3bb5:53616c7465645f5f66cbd1d539b6e51d45efded11e2211fa5e02278855dc86145d4e4891b0e25df9df96fb97a10a9f444f4519f2da4c69c430c5cbf3e9803a1f"

    split := strings.Split(encryptedPwd, ":")

    saltCiphertext, _ := hex.DecodeString(split[1])

    salt := saltCiphertext[8:16]

    ciphertext := saltCiphertext[16:]


    // Get key and IV

    key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte("b676eac8cf70442385dfd4bcfaa61b52"))


    // Decrypt

    block, _ := aes.NewCipher(key)

    plaintext := make([]byte, len(ciphertext))

    stream := cipher.NewCTR(block, iv)

    stream.XORKeyStream(plaintext, ciphertext)


    // Unpad

    unpaddedPlaintext := PKCS7Unpad(plaintext)


    fmt.Println("Decrypted data: ", string(unpaddedPlaintext)) // Decrypted data:  The quick brown fox jumps over the lazy dog

}


func PKCS7Unpad(src []byte) []byte {

    length := len(src)

    unpadding := int(src[length-1])

    return src[:(length - unpadding)]

}

关于安全性:

CryptoJS 执行的密钥和 IV 的派生在EVP_BytesToKey()今天被认为是不安全的。

更安全的替代方法是将第二个参数作为 传递WordArray,以便将其解释为密钥并直接使用。

对于每个加密,必须生成一个随机 IV。

可选地,可靠的密钥派生(例如 PBKDF2)可以与为每个加密随机生成的盐结合使用。

IV 和 salt(都不是秘密)将与密文连接。

最好用GCM代替CTR作为密文,这样可以验证密文的真实性。


查看完整回答
反对 回复 2022-10-24
  • 1 回答
  • 0 关注
  • 133 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信