从节点到 Golang Panic 使用密钥和 IV 解密 AES
Decrypt AES with Secret Key and IV From Node to 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;
};
这里是来自node 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)
}
但它会出现如下恐慌。
panic: cipher.NewCBCDecrypter: IV length must equal block size
goroutine 1 [running]:
crypto/cipher.NewCBCDecrypter({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代码解密有什么问题,谁能帮帮我?我已经使用相同的密钥和 iv,将其从加密密码中拆分出来。
在CryptoJS代码中,crypto.AES.encrypt()
中的第二个参数作为字符串传递,因此被解释为密码。
因此,在加密过程中,首先创建一个八字节的盐,然后使用 KDF EVP_BytesToKey()
.
从中导出密码、密钥和 IV
使用 createRandomIv()
导出并显式传入 crypto.AES.encrypt()
的 IV 将被忽略!
hash.ToString(
) returns OpenSSL 格式的结果由前缀 Salted__ 后跟盐和实际密文组成,所有 Base64 编码。 eHex
包含相同的数据,但编码为十六进制而不是 Base64。
CryptoJS 不会自动禁用 CTR 等流密码模式的填充,因此使用 PKCS#7 填充数据,尽管 CTR 不需要这样做。
在Go代码中,不需要的IV必须先去掉。从剩下的数据中,确定salt和密文。
可以使用 evp.BytesToKeyAES256CBCMD5()
.
从盐和密码短语中检索密钥和 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 和盐(都不是秘密)将与密文连接。
最好使用GCM而不是CTR作为密文,这样可以验证密文的真实性
我在 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;
};
这里是来自node 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)
}
但它会出现如下恐慌。
panic: cipher.NewCBCDecrypter: IV length must equal block size goroutine 1 [running]: crypto/cipher.NewCBCDecrypter({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代码解密有什么问题,谁能帮帮我?我已经使用相同的密钥和 iv,将其从加密密码中拆分出来。
在CryptoJS代码中,crypto.AES.encrypt()
中的第二个参数作为字符串传递,因此被解释为密码。
因此,在加密过程中,首先创建一个八字节的盐,然后使用 KDF EVP_BytesToKey()
.
使用 createRandomIv()
导出并显式传入 crypto.AES.encrypt()
的 IV 将被忽略!
hash.ToString(
) returns OpenSSL 格式的结果由前缀 Salted__ 后跟盐和实际密文组成,所有 Base64 编码。 eHex
包含相同的数据,但编码为十六进制而不是 Base64。
CryptoJS 不会自动禁用 CTR 等流密码模式的填充,因此使用 PKCS#7 填充数据,尽管 CTR 不需要这样做。
在Go代码中,不需要的IV必须先去掉。从剩下的数据中,确定salt和密文。
可以使用 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 使用 EVP_BytesToKey()
执行的密钥和 IV 的推导在今天被认为是不安全的。
更安全的替代方法是将第二个参数作为 WordArray
传递,以便将其解释为密钥并直接使用。
对于每次加密,都必须生成一个随机 IV。
可选地,可靠的密钥派生(例如 PBKDF2)可以与为每次加密随机生成的盐结合使用。
IV 和盐(都不是秘密)将与密文连接。
最好使用GCM而不是CTR作为密文,这样可以验证密文的真实性