从 C# 生成 JavaScript 中的 Rfc2898DeriveBytes 密钥
Generate Rfc2898DeriveBytes key in JavaScript from C#
我有一个使用 Rfc2898DeriveBytes
在 C# 中加密和解密字段的应用程序。我一直在尝试使用 CryptoJS PBKDF2 在 JavaScript 中编写解密方法来制定跨平台解决方案。但是,我一直无法弄清楚以下两个问题:
- 我找不到匹配的钥匙
- 我不知道 JavaScript 中的 IV 应该是什么。
密钥生成
在 C# 中生成密钥(不幸的是我对此没有太多控制,因为它支持很多遗留系统)
private readonly RijndaelManaged _alg = new RijndaelManaged();
public EncryptionManager()
{
var secret = 'D2s1d_5$_t0t3||y_4c3[=11=]m3!1!1!!';
var salt = 'o6805542kcM7c5';
var saltBytes = Encoding.ASCII.GetBytes(salt);
using (var keyDeriver = new Rfc2898DeriveBytes(secret, saltBytes))
{
_alg.Key = keyDeriver.GetBytes(_alg.KeySize / 8); // _alg.KeySize = 256
}
}
我生成密钥的 JS 代码是:
const secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3[=12=]m3!1!1!!');
// Encoding the Salt in from UTF8 to byte array
const salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
// Creating the key in PBKDF2 format to be used during the decryption
const key = CryptoJS.PBKDF2(secret.toString(CryptoJS.enc.Utf8), salt, {
keySize: 128 / 32,
iterations: 1000,
});
根据我的研究,这应该可以理想地工作,但是两种代码中生成的密钥永远不会相同。我花了很多午夜的油来思考我做错了什么,但我不明白为什么。
解密
C#中的解密方法如下:
public string Decrypt(string ciphertext)
{
var cipherTextBytes = Convert.FromBase64String(ciphertext);
var ivSize = BitConverter.ToInt32(cipherTextBytes, 0);
var iv = new byte[ivSize];
var offset = sizeof(int);
Array.Copy(cipherTextBytes, offset, iv, 0, ivSize);
offset += ivSize;
using (var msDecrypt = new MemoryStream(cipherTextBytes, offset, cipherTextBytes.Length - offset))
{
lock (_syncLock)
{
using (var decryptor = _alg.CreateDecryptor(_alg.Key, iv))
using (var decryptStream = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var reader = new StreamReader(decryptStream))
{
return reader.ReadToEnd();
}
}
}
}
我在JS中解密的方案是:
const decrypt = (encryptedData: string): string => {
// Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(encryptedData),
});
// What should be the IV be here?
const iv = CryptoJS.enc.Hex.parse(encryptedData);
// Decrypting the string contained in cipherParams using the PBKDF2 key
const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
mode: CryptoJS.mode.CBC,
// iv,
padding: CryptoJS.pad.Pkcs7,
});
decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedText;
}
我假设如果我能得到正确的密钥和 IV,我就能解决这个问题。我四处寻找兼容的解决方案,但我对自己做错了什么感到茫然。
如有任何帮助,我们将不胜感激。
正如我在评论中提到的,C# 代码 returns 以下 32 字节密钥以及发布的盐和密码(十六进制编码):
e3912cec5e9d1ec5a756cf95991c08f8ce174ee2ddad61c13ff7ece89c0e83e1
CryptoJS 代码仅生成一个 16 字节的密钥。如果使用 keySize: 256 / 32
调整密钥大小,则 CryptoJS 代码 returns 相同 32 字节密钥。
以下 Base64 编码的密文可以使用发布的密码和盐使用 C# 代码解密:
EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=
如果密文是十六进制编码,则结果为:
10000000 232d901eb81dee29fcc488fb2176036a c95a1c69b2c1ba6f8f19d3526b7db12bbd0d6be868b47ac80299dac1685c4e3c5175f8052ccab514979150e0d9347525
前4个字节包含有关IV长度的信息。由于 IV 的长度已知(AES 为 16 个字节),因此实际上没有必要存储此信息,正如 Robert 的评论中已经提到的那样。接下来的 16 个字节对应 IV,其余字节对应实际密文。这些数据必须分开。之后就可以解密了:
// Key derivation
var secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3[=13=]m3!1!1!!');
var salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
var key = CryptoJS.PBKDF2(secret, salt, {
keySize: 256 / 32,
iterations: 1000,
});
// Separation of iv size, iv and ciphertext
var encryptedDataB64 = "EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=";
var encryptedData = CryptoJS.enc.Base64.parse(encryptedDataB64);
var ivSize = CryptoJS.lib.WordArray.create(encryptedData.words.slice(0, 1));
var iv = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1, 1 + 4));
var ciphertext = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1 + 4));
// Decryption
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: ciphertext
});
var decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
mode: CryptoJS.mode.CBC,
iv: iv,
padding: CryptoJS.pad.Pkcs7,
});
var decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
// Output
console.log(decryptedText);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
我有一个使用 Rfc2898DeriveBytes
在 C# 中加密和解密字段的应用程序。我一直在尝试使用 CryptoJS PBKDF2 在 JavaScript 中编写解密方法来制定跨平台解决方案。但是,我一直无法弄清楚以下两个问题:
- 我找不到匹配的钥匙
- 我不知道 JavaScript 中的 IV 应该是什么。
密钥生成
在 C# 中生成密钥(不幸的是我对此没有太多控制,因为它支持很多遗留系统)
private readonly RijndaelManaged _alg = new RijndaelManaged();
public EncryptionManager()
{
var secret = 'D2s1d_5$_t0t3||y_4c3[=11=]m3!1!1!!';
var salt = 'o6805542kcM7c5';
var saltBytes = Encoding.ASCII.GetBytes(salt);
using (var keyDeriver = new Rfc2898DeriveBytes(secret, saltBytes))
{
_alg.Key = keyDeriver.GetBytes(_alg.KeySize / 8); // _alg.KeySize = 256
}
}
我生成密钥的 JS 代码是:
const secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3[=12=]m3!1!1!!');
// Encoding the Salt in from UTF8 to byte array
const salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
// Creating the key in PBKDF2 format to be used during the decryption
const key = CryptoJS.PBKDF2(secret.toString(CryptoJS.enc.Utf8), salt, {
keySize: 128 / 32,
iterations: 1000,
});
根据我的研究,这应该可以理想地工作,但是两种代码中生成的密钥永远不会相同。我花了很多午夜的油来思考我做错了什么,但我不明白为什么。
解密
C#中的解密方法如下:
public string Decrypt(string ciphertext)
{
var cipherTextBytes = Convert.FromBase64String(ciphertext);
var ivSize = BitConverter.ToInt32(cipherTextBytes, 0);
var iv = new byte[ivSize];
var offset = sizeof(int);
Array.Copy(cipherTextBytes, offset, iv, 0, ivSize);
offset += ivSize;
using (var msDecrypt = new MemoryStream(cipherTextBytes, offset, cipherTextBytes.Length - offset))
{
lock (_syncLock)
{
using (var decryptor = _alg.CreateDecryptor(_alg.Key, iv))
using (var decryptStream = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var reader = new StreamReader(decryptStream))
{
return reader.ReadToEnd();
}
}
}
}
我在JS中解密的方案是:
const decrypt = (encryptedData: string): string => {
// Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(encryptedData),
});
// What should be the IV be here?
const iv = CryptoJS.enc.Hex.parse(encryptedData);
// Decrypting the string contained in cipherParams using the PBKDF2 key
const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
mode: CryptoJS.mode.CBC,
// iv,
padding: CryptoJS.pad.Pkcs7,
});
decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedText;
}
我假设如果我能得到正确的密钥和 IV,我就能解决这个问题。我四处寻找兼容的解决方案,但我对自己做错了什么感到茫然。
如有任何帮助,我们将不胜感激。
正如我在评论中提到的,C# 代码 returns 以下 32 字节密钥以及发布的盐和密码(十六进制编码):
e3912cec5e9d1ec5a756cf95991c08f8ce174ee2ddad61c13ff7ece89c0e83e1
CryptoJS 代码仅生成一个 16 字节的密钥。如果使用 keySize: 256 / 32
调整密钥大小,则 CryptoJS 代码 returns 相同 32 字节密钥。
以下 Base64 编码的密文可以使用发布的密码和盐使用 C# 代码解密:
EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=
如果密文是十六进制编码,则结果为:
10000000 232d901eb81dee29fcc488fb2176036a c95a1c69b2c1ba6f8f19d3526b7db12bbd0d6be868b47ac80299dac1685c4e3c5175f8052ccab514979150e0d9347525
前4个字节包含有关IV长度的信息。由于 IV 的长度已知(AES 为 16 个字节),因此实际上没有必要存储此信息,正如 Robert 的评论中已经提到的那样。接下来的 16 个字节对应 IV,其余字节对应实际密文。这些数据必须分开。之后就可以解密了:
// Key derivation
var secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3[=13=]m3!1!1!!');
var salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
var key = CryptoJS.PBKDF2(secret, salt, {
keySize: 256 / 32,
iterations: 1000,
});
// Separation of iv size, iv and ciphertext
var encryptedDataB64 = "EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=";
var encryptedData = CryptoJS.enc.Base64.parse(encryptedDataB64);
var ivSize = CryptoJS.lib.WordArray.create(encryptedData.words.slice(0, 1));
var iv = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1, 1 + 4));
var ciphertext = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1 + 4));
// Decryption
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: ciphertext
});
var decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
mode: CryptoJS.mode.CBC,
iv: iv,
padding: CryptoJS.pad.Pkcs7,
});
var decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
// Output
console.log(decryptedText);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>