从 Node JS Crypto 中的盐和密码为 AES 加密创建 16 字节密钥
Create 16 byte key for AES Encryption from Salt and Password in Node JS Crypto
我正在尝试使用节点 JS 加密模块匹配在 C# 中实现的 AES 256 CBC 加密。
这是我的 C# 代码
using System;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static void Main()
{
Console.WriteLine(EncryptExt("Hello World"));
Console.WriteLine(DecryptExt(EncryptExt("Hello World")));
}
public static string EncryptExt(string raw)
{
using (var csp = new AesCryptoServiceProvider())
{
ICryptoTransform e = GetCryptoTransformExt(csp, true);
byte[] inputBuffer = Encoding.UTF8.GetBytes(raw);
byte[] output = e.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
string encrypted = Convert.ToBase64String(output);
return encrypted;
}
}
public static string DecryptExt(string encrypted)
{
using (var csp = new AesCryptoServiceProvider())
{
var d = GetCryptoTransformExt(csp, false);
byte[] output = Convert.FromBase64String(encrypted);
byte[] decryptedOutput = d.TransformFinalBlock(output, 0, output.Length);
string decypted = Encoding.UTF8.GetString(decryptedOutput);
return decypted;
}
}
private static ICryptoTransform GetCryptoTransformExt(AesCryptoServiceProvider csp, bool encrypting)
{
csp.Mode = CipherMode.CBC;
csp.Padding = PaddingMode.PKCS7;
var passWord = Convert.ToString("AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas");
var salt = Convert.ToString("ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a");
String iv = Convert.ToString("aAB1jhPQ89o=f619");
var spec = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(passWord), Encoding.UTF8.GetBytes(salt), 65536);
byte[] key = spec.GetBytes(16);
csp.IV = Encoding.UTF8.GetBytes(iv);
csp.Key = key;
if (encrypting)
{
return csp.CreateEncryptor();
}
return csp.CreateDecryptor();
}
}
这是我的 Node JS 实现
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
password = 'AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas',
salt = 'ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a',
iv = 'aAB1jhPQ89o=f619',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm,createHashPassword(), iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
function createHashPassword(){
let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 16, 'sha1');
return nodeCrypto || nodeCrypto.toString('hex');
};
function decrypt(encrypted) {
let decipher = crypto.createDecipheriv(algorithm, Buffer.from(createHashPassword(),"hex"), iv)
let dec = decipher.update(encrypted, outputEncoding, inputEncoding)
dec += decipher.final(inputEncoding);
return dec;
}
console.log(encrypt('Hello World'));
console.log(decrypt(encrypt('Hello World')));
这两个选项的加密数据不同,因此无法解决。
到目前为止我看到的是,
- node crypto createCipheriv 方法只需要 32 字节缓冲区,如果我
传递给它一个 16 字节的缓冲区,它说,长度无效。
- 如果我将 16 字节密钥转换为十六进制编码字符串,加密值会发生变化并且与 C# 实现不匹配。
- 我无法更改 C# 实现,因为它已经在生产中并被多个应用程序使用。
- 所以在节点 js 中从盐和密码生成密钥似乎存在问题,与 C# 中所做的匹配,我无法弄清楚。
代码可以在下面link进行测试:
C# 实现:https://dotnetfiddle.net/bClrpW
节点 JS 实现:https://runkit.com/a-vi-nash/5c062544509d8200156f6111
您似乎在 C#
代码中创建了一个 AES-128
实例,因为您使用的是 16 字节 keylen。
AES-256
keylen 是 32 字节,而不是 16 字节。
代码中的错误:
- 由于您在
C#
中为密钥设置了 16 个字节,因此它使用 AES-128
,而不是 AES-256
。所以你需要把node.js
改成AES-128
或者把生成的key改成两边都是32字节。
- 由于您使用的是文本字符串 salt 和密码(未
base64
编码),您的 node.js
方使用了不正确的 pbkdf2Sync
参数。
IV
len for AES
algorithm in 16 bytes and you cannot use shorter ones.
既然你想要AES-256
这里是你对双方的改变:
C#
边:
String iv = Convert.ToString("SOME_IV_SOME_IV_"); // 16 bytes IV
....
byte[] key = spec.GetBytes(32); // 32 bytes key
node.js
边:
iv = 'SOME_IV_SOME_IV_' // 16 bytes IV similar to C#
...
// Bugs in this function
function createHashPassword(){
// Change parameters to `base64` only if salt and password are base64. it may be true for salt, but it is can rarely be correct for password.
let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 32, 'sha1');
return nodeCrypto;
};
重要说明:
- 请记住,
IV
必须选择为随机缓冲区(既不是固定的也不是文本),因为看起来您正在通过网络发送它,所以您也需要发送 IV
。
SALT
必须是随机缓冲区(不是文本)并且两边固定。
- 我建议至少对
PBKDF2
使用超过 100000 次迭代。
如果您使用长度为 41 个字符的密码,为什么不使用实际的密钥?一个 base64 编码的 256 位密钥长度为 44 个字符。
加盐和偏差迭代的目的是为了应对密码过短的常见问题。但是,为什么要在没有任何额外好处的情况下费尽心思地在两端实现这一点,却有不止一个缺点——比如更多的代码和更慢的解决方案。
我正在尝试使用节点 JS 加密模块匹配在 C# 中实现的 AES 256 CBC 加密。
这是我的 C# 代码
using System;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static void Main()
{
Console.WriteLine(EncryptExt("Hello World"));
Console.WriteLine(DecryptExt(EncryptExt("Hello World")));
}
public static string EncryptExt(string raw)
{
using (var csp = new AesCryptoServiceProvider())
{
ICryptoTransform e = GetCryptoTransformExt(csp, true);
byte[] inputBuffer = Encoding.UTF8.GetBytes(raw);
byte[] output = e.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
string encrypted = Convert.ToBase64String(output);
return encrypted;
}
}
public static string DecryptExt(string encrypted)
{
using (var csp = new AesCryptoServiceProvider())
{
var d = GetCryptoTransformExt(csp, false);
byte[] output = Convert.FromBase64String(encrypted);
byte[] decryptedOutput = d.TransformFinalBlock(output, 0, output.Length);
string decypted = Encoding.UTF8.GetString(decryptedOutput);
return decypted;
}
}
private static ICryptoTransform GetCryptoTransformExt(AesCryptoServiceProvider csp, bool encrypting)
{
csp.Mode = CipherMode.CBC;
csp.Padding = PaddingMode.PKCS7;
var passWord = Convert.ToString("AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas");
var salt = Convert.ToString("ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a");
String iv = Convert.ToString("aAB1jhPQ89o=f619");
var spec = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(passWord), Encoding.UTF8.GetBytes(salt), 65536);
byte[] key = spec.GetBytes(16);
csp.IV = Encoding.UTF8.GetBytes(iv);
csp.Key = key;
if (encrypting)
{
return csp.CreateEncryptor();
}
return csp.CreateDecryptor();
}
}
这是我的 Node JS 实现
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
password = 'AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas',
salt = 'ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a',
iv = 'aAB1jhPQ89o=f619',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm,createHashPassword(), iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
function createHashPassword(){
let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 16, 'sha1');
return nodeCrypto || nodeCrypto.toString('hex');
};
function decrypt(encrypted) {
let decipher = crypto.createDecipheriv(algorithm, Buffer.from(createHashPassword(),"hex"), iv)
let dec = decipher.update(encrypted, outputEncoding, inputEncoding)
dec += decipher.final(inputEncoding);
return dec;
}
console.log(encrypt('Hello World'));
console.log(decrypt(encrypt('Hello World')));
这两个选项的加密数据不同,因此无法解决。
到目前为止我看到的是,
- node crypto createCipheriv 方法只需要 32 字节缓冲区,如果我 传递给它一个 16 字节的缓冲区,它说,长度无效。
- 如果我将 16 字节密钥转换为十六进制编码字符串,加密值会发生变化并且与 C# 实现不匹配。
- 我无法更改 C# 实现,因为它已经在生产中并被多个应用程序使用。
- 所以在节点 js 中从盐和密码生成密钥似乎存在问题,与 C# 中所做的匹配,我无法弄清楚。
代码可以在下面link进行测试: C# 实现:https://dotnetfiddle.net/bClrpW 节点 JS 实现:https://runkit.com/a-vi-nash/5c062544509d8200156f6111
您似乎在 C#
代码中创建了一个 AES-128
实例,因为您使用的是 16 字节 keylen。
AES-256
keylen 是 32 字节,而不是 16 字节。
代码中的错误:
- 由于您在
C#
中为密钥设置了 16 个字节,因此它使用AES-128
,而不是AES-256
。所以你需要把node.js
改成AES-128
或者把生成的key改成两边都是32字节。 - 由于您使用的是文本字符串 salt 和密码(未
base64
编码),您的node.js
方使用了不正确的pbkdf2Sync
参数。 IV
len forAES
algorithm in 16 bytes and you cannot use shorter ones.
既然你想要AES-256
这里是你对双方的改变:
C#
边:
String iv = Convert.ToString("SOME_IV_SOME_IV_"); // 16 bytes IV
....
byte[] key = spec.GetBytes(32); // 32 bytes key
node.js
边:
iv = 'SOME_IV_SOME_IV_' // 16 bytes IV similar to C#
...
// Bugs in this function
function createHashPassword(){
// Change parameters to `base64` only if salt and password are base64. it may be true for salt, but it is can rarely be correct for password.
let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 32, 'sha1');
return nodeCrypto;
};
重要说明:
- 请记住,
IV
必须选择为随机缓冲区(既不是固定的也不是文本),因为看起来您正在通过网络发送它,所以您也需要发送IV
。 SALT
必须是随机缓冲区(不是文本)并且两边固定。- 我建议至少对
PBKDF2
使用超过 100000 次迭代。
如果您使用长度为 41 个字符的密码,为什么不使用实际的密钥?一个 base64 编码的 256 位密钥长度为 44 个字符。
加盐和偏差迭代的目的是为了应对密码过短的常见问题。但是,为什么要在没有任何额外好处的情况下费尽心思地在两端实现这一点,却有不止一个缺点——比如更多的代码和更慢的解决方案。