【问题标题】:Decrypt AES with Secret Key and IV From Node to Golang Panic使用密钥和 IV 从节点解密 AES 到 Golang Panic
【发布时间】:2022-01-08 03:19:22
【问题描述】:

我在 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"
)

func main() {
    secretKey := "b676eac8cf70442385dfd4bcfaa61b52"
    encryptedPwd := "2db5c01b4825b6d4dd7a7b96f04f3bb5:53616c7465645f5f691671363cda1b9d05ee6bdd637e1e99bc3b29ef2ad7ec53"
    split := strings.Split(encryptedPwd, ":")

    c, _ := aes.NewCipher([]byte(secretKey))
    cfbdec := cipher.NewCBCDecrypter(c, []byte(split[0]))
    plaintext := make([]byte, len(split[1]))
    cfbdec.CryptBlocks(plaintext, []byte(split[1]))
    fmt.Println(plaintext)
}

但它恐慌如下。

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

更新 1

我在不使用 iv 的情况下将代码更新为解密,但结果不可读。

split := strings.Split(encryptedPwd, ":")
ciphertext, _ := hex.DecodeString(split[1])
c, _ := aes.NewCipher([]byte(secretKey))
plaintext := make([]byte, len(ciphertext))
c.Decrypt(plaintext, []byte(ciphertext))
fmt.Println(string(plaintext))

|A/��c�*Z�S/�x

我的golang代码中的解密有什么问题,有人可以帮我吗?我已经使用了相同的密钥,并且通过将其从加密密码中拆分出来。

【问题讨论】:

  • 如果你登录encryptPassword()这两个值,ivhash.iv.toString(),你会发现它们是不同的,即在加密过程中忽略了用createRandomIv()精心确定的IV。
  • @Topaco 什么意思,iv 在冒号前第一段的加密密码中,而且我已经使用相同的密钥和iv,那么如何修复它?
  • 我建议你先用 CryptoJS 解密密文。然后你会注意到用createRandomIv()创建的IV不需要解密。
  • 你为什么还要在 Node.js 上使用 CryptoJS? Node.js 有built-in crypto
  • @Topaco 我用使用 iv 的 nodejs 上的解密函数更新了问题

标签: node.js go encryption aes


【解决方案1】:

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

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

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

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

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


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

可以使用evp.BytesToKeyAES256CBCMD5() 从 salt 和密码短语中检索密钥和 IV。

使用密钥和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 使用 EVP_BytesToKey() 执行的密钥和 IV 派生在今天被认为是不安全的。
更安全的替代方法是将第二个参数作为WordArray 传递,以便将其解释为密钥并直接使用。
对于每个加密,必须生成一个随机 IV。
或者,可以将可靠的密钥派生(例如 PBKDF2)与为每次加密随机生成的盐结合使用。
IV 和 salt(都不是秘密)将与密文连接。
密文最好用GCM代替CTR,这样可以验证密文的真实性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-21
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    • 1970-01-01
    • 2014-04-30
    相关资源
    最近更新 更多