AES解密中IV值的默认选择方法

发布时间 2023-03-25 22:58:23作者: Fang20

说明

在重构一个 Node.js 项目变为 Go 的过程中,我遇到了一个问题,无法正确复写其中一个使用的 AES 对称加密。原来的项目只需要两个参数就能成功解密,但我现在无法复现这个结果。

CryptoJS.AES.decrypt(encodeData, passphrase)

经过半天的尝试和折腾,最终我在网上找到了一个方法,通过计算 IV 值才成功地将加密数据解密开。

搜索信息

我开始在网上搜索是否有默认的 IV 值,并了解到 CryptoJS 使用的是 CBC 模式、AES-256 加密和 PKCS7 填充。但是发现一些在线 AES 解密工具需要手动填写 IV 值才能顺利解密数据。

这让我痛苦了一段时间,在解密工具疯狂尝试0,0x00这类做 IV 默认值?,尝试无果继续搜索在 CryptoJS 官网 看到了如下内容:

我感觉是 CryptoJS 通过调用 OpenSSL 来实现加密的,于是我开始在网上搜索如何使用 OpenSSL 生成 IV 值。最终我找到了一篇帖子,详细介绍了这个过程(可惜,我现在找不到原来的链接了)。

解密过程

首先我使用 Base64 将加密字符串解码,发现其开头为 "Salted__"。在这个前缀后面的八个字符就是 salt,使用该 salt 与 passphrase 可以计算出 key 和 IV 值。下面是计算过程的伪代码:

hash1 = md5(key + salt)
hash2 = md5(hash1 + key + salt)
hash3 = md5(hash2 + key + salt)
calKey = hash1 + hash2
iv = hash3

接着去除开头的前缀和 salt,得到加密字符串。然后我使用之前计算出来的 key 和 IV 值对该字符串进行 AES-256 解密,最后再去除填充字符即可。

这里是我使用 Go 实现该过程的代码,供您参考:

func AES256Decode(encodeStr string, key string) (string, error) {
    // base64Decode
    ciphertext, err := base64.StdEncoding.DecodeString(encodeStr)
    if err != nil {
        return "", err
    }

    // 这个方法里面是上面所说的伪代码部分
    iv, calKey := getIVAndKey(ciphertext, key)
    block, err := aes.NewCipher(calKey)
    if err != nil {
        return "", err
    }

    mode := cipher.NewCBCDecrypter(block, iv)

    // 去除前缀与salt
    ciphertext = ciphertext[16:]
    plaintext := make([]byte, len(ciphertext))
    mode.CryptBlocks(plaintext, ciphertext)

    // 去除填充
    paddingLen := int(plaintext[len(plaintext)-1])
    if paddingLen > len(plaintext) {
        return "", errors.New("padding len error")
    }

    return string(plaintext[:len(plaintext)-paddingLen]), nil
}