1 回答
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作为密文,这样可以验证密文的真实性。
- 1 回答
- 0 关注
- 203 浏览
添加回答
举报