使用crypto模块将遗留C# Rijndael加密算法移植到nodejs

j8ag8udp  于 2023-08-04  发布在  Node.js
关注(0)|答案(1)|浏览(120)

我正在将一个C# .NET 4.8 Framework代码库迁移到云。新的体系结构使用nodejs firebase函数。这些函数只允许nodejs代码。
下面的代码用于使用存储在数据库中的哈希值验证输入文本密码。该应用程序正在迁移到SSO解决方案,但将允许用户通过验证其以前的凭据来从其旧帐户中索取数据。
下面的代码使用RFC2898(现在称为PBKDF 2)生成密钥和初始化向量,并将其提供给Rijndael对象以设置加密。
此方法出于某种原因将密码与salt组合在一起,可能是为了增加密码的长度。salt始终为36个字符。然后,它使用了胡椒的地方,盐通常会去。最后,加密值以base64字符串的形式输出。

using System.Security.Cryptography;
using System.Text;

var password = "password123";
var salt = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var pepper = "yyyyyyyy";

var enc = new UTF8Encoding();
var keyGen = new Rfc2898DeriveBytes(password + salt, enc.GetBytes(pepper));

var key = keyGen.GetBytes(32);
var iv = keyGen.GetBytes(16);

var cipher = new RijndaelManaged
{
  Key = key,
  IV = iv
};

Console.WriteLine("Key: " + Convert.ToBase64String(key));
Console.WriteLine("IV: " + Convert.ToBase64String(iv));

var plainText = enc.GetBytes(password + salt);
using var encryptor = cipher.CreateEncryptor();
using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);

cs.Write(plainText, 0, plainText.Length);
cs.FlushFinalBlock();

Console.WriteLine("Hash: " + Convert.ToBase64String(ms.ToArray()));

字符串
使用nodejs 18和crypto包,我能够使用下面的代码创建与上面的C#代码等效的密钥和iv。

const generatePbkdf2Values = (password, pepper) => {
  const pepperBytes = Buffer.from(pepper, 'utf8');

  const iterations = 1000;
  const length = 32 + 16;
  const digest = 'sha1';
  const keyPlusIv = crypto.pbkdf2Sync(
    password,
    pepperBytes,
    iterations,
    length,
    digest
  );

  return {
    key: keyPlusIv.subarray(0, 32),
    iv: keyPlusIv.subarray(32, length),
  };
};


我读过Rijndael是一个具有固定块大小的variant of AES。因此,我希望能够使用crypto包的createCipheriv方法来加密文本,但我很难做到这一点。我担心C#设置或默认值不兼容,或者我没有正确配置它们。我想听听你的建议。
一些假设

  1. IV是一个16字节/128位密钥,需要aes-128-cbc算法。但是128算法会抛出密钥长度错误,因此它必须基于需要aes-256-cbc算法的32字节/256位密钥。
    1.默认情况下,C# Rijndael算法使用PKCS 7填充,crypto.createCipheriv通过cipher也使用PKCS 7填充。setAutoPadding(true)
  2. nodejs方法中的key和iv创建的值与C#等效
    下面是我用来散列文本的nodej。keyivpasswordsalt来自上面的代码段。
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

const passwordAndSalt = Buffer.from(password + salt, 'utf8');
const encoding = 'hex';
const inputEncoding = encoding;
const outputEncoding = encoding

let result = cipher.update(passwordAndSalt, inputEncoding, outputEncoding);
result += cipher.final(outputEncoding);

console.log(result.toString('base64'));


这会提供比C#方案多32个字符的字串,而且不共用任何模式。
具有上述值的正确哈希值为DC07eaMXkaeK26KTIt3ldtzTdISTH5j9cmrg3OzFF16MvfKkEi9ihEkI8VDLzNvU
作为B计划,我尝试使用rinjndael-js npm包。下面的代码使用与上面相同的密钥和iv,生成的哈希值几乎正确,但不完全正确。

import Rijndael from 'rijndael-js';

const cipher = new Rijndael(key, 'cbc');
const text = Buffer.from(password + salt, 'utf8');

return Buffer.from(cipher.encrypt(text, 16, iv)).toString('base64')


这会产生长度相同且68%相等的杂凑。前43个字符匹配,后21个字符不匹配。这将是我的首选使用内置的加密包来创建相同的散列。有可能吗?你能发现我的错误吗?先谢了。

vngu2lb8

vngu2lb81#

好的,我发现了bug,它与编码、响应类型和连接有关。

工作溶液

const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

  const passwordAndSalt = Buffer.from(password + salt, 'utf8');
  const inputEncoding = 'hex';

  const result = Buffer.concat([cipher.update(passwordAndSalt, inputEncoding), cipher.final()]);

  return result.toString('base64');

字符串
原因
上面的结果长了50%,因为调用.final('hex')并追加到result对象是将两个hex对象组合在一起。我假设这会导致在尝试将结果显示为.toString('base64')时出现问题。你甚至能够.toString('base64')十六进制值吗?
如果未提供输出编码,则会传回Buffer。因此,连接缓冲区并对其进行base64编码可提供正确的哈希值!

编辑

这简化了返回base64的过程,并得到了@Topaco的建议。

const outputEncoding = 'base64';
  
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  
  const passwordAndSalt = Buffer.from(password + salt, 'utf8');

  let result = cipher.update(passwordAndSalt, null, outputEncoding);
  result += cipher.final(outputEncoding);

  return result;

相关问题