Node JS 和 C# 中的 AES 加密给出了不同的结果
AES encryption in Node JS and C# gives different results
我有一个用例,其中必须使用 AES 256 算法对文本进行编码和发送。客户端代码在 C# 中,它将解密代码。
JS加密代码:
const crypto = require('crypto');
algorithm = 'aes-256-cbc',
secret = '1234567890123456',
keystring = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 16);
iv = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 16);
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv('aes-256-cbc', keystring, iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
客户端使用的更新代码:
var keybytes = Encoding.UTF8.GetBytes(passwordKey);
var iv = Encoding.UTF8.GetBytes(passwordKey);
private byte[] EncryptStringToBytes(string plainText, byte[] key, byte[] iv)
{
try
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
{
throw new ArgumentNullException("plainText");
}
if (key == null || key.Length <= 0)
{
throw new ArgumentNullException("key");
}
if (iv == null || iv.Length <= 0)
{
throw new ArgumentNullException("key");
}
byte[] encrypted;
// Create a RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
// Create a decrytor to perform the stream transform.
var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
catch (Exception ex)
{
throw ex;
//LoggerCS.logError("Utility", "EncryptStringToBytes", JsonConvert.SerializeObject(null), ex.ToString(), ex.StackTrace);
}
return null;
}
C#中使用的keyString和IV值是一样的,都是用Utf8加密的。在 Node JS 中寻找等效操作。
TLDR;
您使用的是不同的 IV 和算法(AES-128 与 AES-256),因此您会得到不同的结果...
如果您想获得相同的结果,您将需要使用相同的 IV 以及相同的密钥和算法。 这将是一个反模式(即不要这样做)!检查 John 关于您如何忽略算法变量的评论代码,快速浏览一下,这个和不同的 IV 是导致您得到不同结果的原因。
更长的答案;
1) 你确实想要它,这样用相同密钥加密的相同消息(纯文本)并不总是产生相同的加密结果(密文)。否则,任何窃听方都将始终知道何时再次发送了重复消息。
2) 初始化向量 (IV) 用于提供随机性,因此在使用给定密钥时,相同的明文并不总是产生相同的密文。
3) 这意味着要解密消息,您不仅需要知道密钥,还需要知道 IV。
4) IV 应该是随机的,而不是确定性地从密钥派生的,否则每次使用相同的密钥都会有相同的 IV,因此每次对相同的明文进行加密都会产生相同的密文。 (让您容易受到窃听者观察收到给定消息的结果并开始确定消息的含义)。
查看此问题的答案 AES Encryption - Key versus IV and also this Wikipedia entry http://en.wikipedia.org/wiki/Initialization_vector 了解更多信息。
试试这个:
var crypto = require('text');
var mykey = crypto.createCipher('aes-256-cbc', keystring, iv);
var mystr = mykey.update('abc', 'utf8', 'hex')
mystr += mykey.final('hex');
console.log(mystr);
整个问题很可能与编码有关。 .Net crypto class 默认使用 Unicode 或 utf16-le。当您指定 base64 作为输出编码时。
改成utf16le
即
outputEncoding = 'utf16le';
决议比预期的要简单。 RijndaelManaged code with keylen 128 refered AES-128 algorithm and using aes-128-cbc in nodeJS.
此外,由于 C# 代码使用 getBytes 作为键和 iv 值。 Buffer.from(secret) 必须用于 nodeJS 中的键和 iv 值。
最终解决方案如下:
const crypto = require('crypto');
algorithm = "aes-128-cbc",
secret = '1234567890123456',
keystring = new Buffer(secret),
iv = new Buffer(secret),
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm,keystring, iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
function decrypt(encrypted) {
let decipher = crypto.createDecipheriv(algorithm,keystring, iv)
let dec = decipher.update(encrypted, outputEncoding, inputEncoding)
dec += decipher.final(inputEncoding);
return dec;
}
我有一个用例,其中必须使用 AES 256 算法对文本进行编码和发送。客户端代码在 C# 中,它将解密代码。
JS加密代码:
const crypto = require('crypto');
algorithm = 'aes-256-cbc',
secret = '1234567890123456',
keystring = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 16);
iv = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 16);
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv('aes-256-cbc', keystring, iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
客户端使用的更新代码:
var keybytes = Encoding.UTF8.GetBytes(passwordKey);
var iv = Encoding.UTF8.GetBytes(passwordKey);
private byte[] EncryptStringToBytes(string plainText, byte[] key, byte[] iv)
{
try
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
{
throw new ArgumentNullException("plainText");
}
if (key == null || key.Length <= 0)
{
throw new ArgumentNullException("key");
}
if (iv == null || iv.Length <= 0)
{
throw new ArgumentNullException("key");
}
byte[] encrypted;
// Create a RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
// Create a decrytor to perform the stream transform.
var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
catch (Exception ex)
{
throw ex;
//LoggerCS.logError("Utility", "EncryptStringToBytes", JsonConvert.SerializeObject(null), ex.ToString(), ex.StackTrace);
}
return null;
}
C#中使用的keyString和IV值是一样的,都是用Utf8加密的。在 Node JS 中寻找等效操作。
TLDR;
您使用的是不同的 IV 和算法(AES-128 与 AES-256),因此您会得到不同的结果...
如果您想获得相同的结果,您将需要使用相同的 IV 以及相同的密钥和算法。 这将是一个反模式(即不要这样做)!检查 John 关于您如何忽略算法变量的评论代码,快速浏览一下,这个和不同的 IV 是导致您得到不同结果的原因。
更长的答案;
1) 你确实想要它,这样用相同密钥加密的相同消息(纯文本)并不总是产生相同的加密结果(密文)。否则,任何窃听方都将始终知道何时再次发送了重复消息。
2) 初始化向量 (IV) 用于提供随机性,因此在使用给定密钥时,相同的明文并不总是产生相同的密文。
3) 这意味着要解密消息,您不仅需要知道密钥,还需要知道 IV。
4) IV 应该是随机的,而不是确定性地从密钥派生的,否则每次使用相同的密钥都会有相同的 IV,因此每次对相同的明文进行加密都会产生相同的密文。 (让您容易受到窃听者观察收到给定消息的结果并开始确定消息的含义)。
查看此问题的答案 AES Encryption - Key versus IV and also this Wikipedia entry http://en.wikipedia.org/wiki/Initialization_vector 了解更多信息。
试试这个:
var crypto = require('text');
var mykey = crypto.createCipher('aes-256-cbc', keystring, iv);
var mystr = mykey.update('abc', 'utf8', 'hex')
mystr += mykey.final('hex');
console.log(mystr);
整个问题很可能与编码有关。 .Net crypto class 默认使用 Unicode 或 utf16-le。当您指定 base64 作为输出编码时。 改成utf16le 即
outputEncoding = 'utf16le';
决议比预期的要简单。 RijndaelManaged code with keylen 128 refered AES-128 algorithm and using aes-128-cbc in nodeJS.
此外,由于 C# 代码使用 getBytes 作为键和 iv 值。 Buffer.from(secret) 必须用于 nodeJS 中的键和 iv 值。 最终解决方案如下:
const crypto = require('crypto');
algorithm = "aes-128-cbc",
secret = '1234567890123456',
keystring = new Buffer(secret),
iv = new Buffer(secret),
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm,keystring, iv);
let encrypted = cipher.update(text, inputEncoding, outputEncoding)
encrypted += cipher.final(outputEncoding);
return encrypted;
}
function decrypt(encrypted) {
let decipher = crypto.createDecipheriv(algorithm,keystring, iv)
let dec = decipher.update(encrypted, outputEncoding, inputEncoding)
dec += decipher.final(inputEncoding);
return dec;
}