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

我在 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 main


import (

    "crypto/aes"

    "crypto/cipher"

    "fmt"

    "strings"

)

}

但它恐慌如下。


恐慌:cipher.NewCBCDecrypter:IV 长度必须等于块大小 goroutine 1 [运行]:crypto/cipher.NewCBCDecrypter({0x10c4ee8, 0xc000066060}, {0xc00001e040, 0x1, 0x20})



波斯汪
浏览 137回答 1
1回答

qq_笑_17

在 CryptoJS 代码中,第二个参数 incrypto.AES.encrypt()作为字符串传递,因此它被解释为密码短语。因此,在加密期间,首先创建一个 8 字节的 salt,然后使用 KDF 导出密码短语、密钥和 IV&nbsp;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&nbsp;evp.BytesToKeyAES256CBCMD5()。使用密钥和 IV 可以使用 AES-CTR 进行解密。最后,必须删除 PKCS#7 填充。下面的 Go 代码实现了这些步骤。输入数据是使用 NodeJS 代码生成的:import (&nbsp; &nbsp; "crypto/aes"&nbsp; &nbsp; "crypto/cipher"&nbsp; &nbsp; "encoding/hex"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "strings"&nbsp; &nbsp; "github.com/walkert/go-evp")func main() {&nbsp; &nbsp; // Determine salt and actual ciphertext&nbsp; &nbsp; encryptedPwd := "2db5c01b4825b6d4dd7a7b96f04f3bb5:53616c7465645f5f66cbd1d539b6e51d45efded11e2211fa5e02278855dc86145d4e4891b0e25df9df96fb97a10a9f444f4519f2da4c69c430c5cbf3e9803a1f"&nbsp; &nbsp; split := strings.Split(encryptedPwd, ":")&nbsp; &nbsp; saltCiphertext, _ := hex.DecodeString(split[1])&nbsp; &nbsp; salt := saltCiphertext[8:16]&nbsp; &nbsp; ciphertext := saltCiphertext[16:]&nbsp; &nbsp; // Get key and IV&nbsp; &nbsp; key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte("b676eac8cf70442385dfd4bcfaa61b52"))&nbsp; &nbsp; // Decrypt&nbsp; &nbsp; block, _ := aes.NewCipher(key)&nbsp; &nbsp; plaintext := make([]byte, len(ciphertext))&nbsp; &nbsp; stream := cipher.NewCTR(block, iv)&nbsp; &nbsp; stream.XORKeyStream(plaintext, ciphertext)&nbsp; &nbsp; // Unpad&nbsp; &nbsp; unpaddedPlaintext := PKCS7Unpad(plaintext)&nbsp; &nbsp; fmt.Println("Decrypted data: ", string(unpaddedPlaintext)) // Decrypted data:&nbsp; The quick brown fox jumps over the lazy dog}func PKCS7Unpad(src []byte) []byte {&nbsp; &nbsp; length := len(src)&nbsp; &nbsp; unpadding := int(src[length-1])&nbsp; &nbsp; return src[:(length - unpadding)]}关于安全性:CryptoJS 执行的密钥和 IV 的派生在EVP_BytesToKey()今天被认为是不安全的。更安全的替代方法是将第二个参数作为 传递WordArray,以便将其解释为密钥并直接使用。对于每个加密,必须生成一个随机 IV。可选地,可靠的密钥派生(例如 PBKDF2)可以与为每个加密随机生成的盐结合使用。IV 和 salt(都不是秘密)将与密文连接。最好用GCM代替CTR作为密文,这样可以验证密文的真实性。
打开App,查看更多内容
随时随地看视频慕课网APP