AES解密中IV值的默认选择方法
说明
在重构一个 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 } func getIVAndKey(ciphertext []byte, key string) (iv []byte, calKey []byte) { salt := ciphertext[8:16] hash1 := md5.Sum([]byte(key + string(salt))) hash2 := md5.Sum(append(hash1[:], []byte(key+string(salt))...)) hash3 := md5.Sum(append(hash2[:], []byte(key+string(salt))...)) calKey = append(hash1[:], hash2[:]...) iv = hash3[:] return }