nodejs中的密钥长度无效,但是C#中的有效密钥长度

发布于 2025-01-23 03:25:21 字数 12326 浏览 2 评论 0 原文

我正在将Rijndael解密从C#转换为Nodejs。

使用的键(或密码)长13个字符。使用的IV是17个字符长。
注意:下面我无法控制长度选择

是C#中的rijndael解密

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
                    
public class Program
{
    public class CryptoProvider
    {
        private ICryptoTransform encryptor = (ICryptoTransform)null;
        private ICryptoTransform decryptor = (ICryptoTransform)null;
        private int minSaltLen = -1;
        private int maxSaltLen = -1;
        
        public CryptoProvider(string passPhrase, string initVector)
          : this(passPhrase, initVector, -1, -1, -1, (string)null, (string)null, 3)
        {
        }

        public CryptoProvider(
          string passPhrase,
          string initVector,
          int minSaltLen,
          int maxSaltLen,
          int keySize,
          string hashAlgorithm,
          string saltValue,
          int passwordIterations)
        {
            this.minSaltLen = 4; 
            this.maxSaltLen = 8;
            keySize = 256;
            hashAlgorithm = "SHA512";

            byte[] rgbIV = Encoding.ASCII.GetBytes(initVector);
            byte[] rgbSalt = new byte[0];
            byte[] bytes = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);

            RijndaelManaged rijndaelManaged = new RijndaelManaged();

            if (rgbIV.Length == 0)
                rijndaelManaged.Mode = CipherMode.ECB;
            else
                rijndaelManaged.Mode = CipherMode.CBC;

            this.encryptor = rijndaelManaged.CreateEncryptor(bytes, rgbIV);
            this.decryptor = rijndaelManaged.CreateDecryptor(bytes, rgbIV);
        }

        public string Decrypt(string cipherText) {
            return this.Decrypt(Convert.FromBase64String(cipherText));
        }
        
        public string Decrypt(byte[] cipherTextBytes) {
            return Encoding.UTF8.GetString(this.DecryptToBytes(cipherTextBytes));
        }

        public byte[] DecryptToBytes(string cipherText) {
            return this.DecryptToBytes(Convert.FromBase64String(cipherText));
        }

        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            int num = 0;
            int sourceIndex = 0;
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            byte[] numArray = new byte[cipherTextBytes.Length];
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.decryptor, CryptoStreamMode.Read);
                num = cryptoStream.Read(numArray, 0, numArray.Length);
                memoryStream.Close();
                cryptoStream.Close();
            }
            if (this.maxSaltLen > 0 && this.maxSaltLen >= this.minSaltLen)
                sourceIndex = (int)numArray[0] & 3 | (int)numArray[1] & 12 | (int)numArray[2] & 48 | (int)numArray[3] & 192;
            byte[] destinationArray = new byte[num - sourceIndex];
            Array.Copy((Array)numArray, sourceIndex, (Array)destinationArray, 0, num - sourceIndex);
            return destinationArray;
        }
    }
    
    public static void Main()
        {
            string Key = "";
            string IV = "";

            string encryptedUserData = "u7uENpFfpQhMXiTThL/ajA==";
            string decryptedUserData;

            CryptoProvider crypto = new CryptoProvider(Key, IV);
            decryptedUserData = crypto.Decrypt(encryptedUserData.Trim());

            Console.WriteLine(decryptedUserData);

        }
}

,由于某种原因,我可以在 dotnetfiddle ,但在Visual Studio中不使用(因为它返回'指定的初始化矢量(IV)的错误与此算法的块大小不匹配。(参数''(参数' rgbiv')'

以下是我尝试使用 rijndael-js 库:

const Rijndael = require("rijndael-js");

const key = "";
const iv = "";

const cipher = new Rijndael(key, "cbc");

const ciphertext = "u7uENpFfpQhMXiTThL/ajA==";

const plaintext = Buffer.from(cipher.decrypt(ciphertext, 256, iv));

返回不支持的密钥大小的错误:104位

所有错误指向同一件事:无效的键/IV长度

。接受键,而IV是有效的


长度 /a/54722065/12278028“> nodejs的实现,并比较了C#的结果,它们相等。

我更新了nodejs实现(请参阅)并注意到了几件事:

  1. 所有由此产生的密文都是相同的。我猜这是盐。
  2. 我尝试解密从C#生成的密文,但似乎有几个字符的左侧。 示例:c#加密字符串: zaqv5w/gwt0sfyxzex+awg == ,nodejs解密字符串:>& .4423
  3. 当我尝试解密的ciphertext时,在C#中,C#编译器返回 System.Security.Cryptography.CryptographiceXception的错误:填充物无效,无法删除。

edit> edit:

c#code:c#code(可使用.NET框架4.7.2):

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

namespace ProgramEncrypt
{
    public class CryptoProvider
    {
        private ICryptoTransform encryptor = (ICryptoTransform)null;
        private ICryptoTransform decryptor = (ICryptoTransform)null;
        private int minSaltLen = -1;
        private int maxSaltLen = -1;

        public CryptoProvider(string passPhrase, string initVector) : this(passPhrase, initVector, -1, -1, -1, (string)null, (string)null, 3) { }

        public CryptoProvider(
          string passPhrase,
          string initVector,
          int minSaltLen,
          int maxSaltLen,
          int keySize,
          string hashAlgorithm,
          string saltValue,
          int passwordIterations)
        {
            this.minSaltLen = 4;
            this.maxSaltLen = 8;
            keySize = 256;
            hashAlgorithm = "SHA512";

            byte[] rgbIV = Encoding.ASCII.GetBytes(initVector);
            byte[] rgbSalt = new byte[0];
            byte[] bytes = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);

            RijndaelManaged rijndaelManaged = new RijndaelManaged();

            if (rgbIV.Length == 0)
                rijndaelManaged.Mode = CipherMode.ECB;
            else
                rijndaelManaged.Mode = CipherMode.CBC;

            this.encryptor = rijndaelManaged.CreateEncryptor(bytes, rgbIV);
            this.decryptor = rijndaelManaged.CreateDecryptor(bytes, rgbIV);
        }

        public string Encrypt(string plainText) => this.Encrypt(Encoding.UTF8.GetBytes(plainText));

        public string Encrypt(byte[] plainTextBytes) => Convert.ToBase64String(this.EncryptToBytes(plainTextBytes));

        public byte[] EncryptToBytes(string plainText) => this.EncryptToBytes(Encoding.UTF8.GetBytes(plainText));

        public byte[] EncryptToBytes(byte[] plainTextBytes)
        {
            byte[] buffer = this.AddSalt(plainTextBytes);
            MemoryStream memoryStream = new MemoryStream();
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.encryptor, CryptoStreamMode.Write);
                cryptoStream.Write(buffer, 0, buffer.Length);
                cryptoStream.FlushFinalBlock();
                byte[] array = memoryStream.ToArray();
                memoryStream.Close();
                cryptoStream.Close();
                return array;
            }
        }

        public string Decrypt(string cipherText) => this.Decrypt(Convert.FromBase64String(cipherText));

        public string Decrypt(byte[] cipherTextBytes) => Encoding.UTF8.GetString(this.DecryptToBytes(cipherTextBytes));

        public byte[] DecryptToBytes(string cipherText) => this.DecryptToBytes(Convert.FromBase64String(cipherText));

        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            int num = 0;
            int sourceIndex = 0;
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            byte[] numArray = new byte[cipherTextBytes.Length];
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.decryptor, CryptoStreamMode.Read);
                num = cryptoStream.Read(numArray, 0, numArray.Length);
                memoryStream.Close();
                cryptoStream.Close();
            }
            if (this.maxSaltLen > 0 && this.maxSaltLen >= this.minSaltLen)
                sourceIndex = (int)numArray[0] & 3 | (int)numArray[1] & 12 | (int)numArray[2] & 48 | (int)numArray[3] & 192;
            byte[] destinationArray = new byte[num - sourceIndex];
            Array.Copy((Array)numArray, sourceIndex, (Array)destinationArray, 0, num - sourceIndex);
            return destinationArray;
        }

        private byte[] AddSalt(byte[] plainTextBytes)
        {
            if (this.maxSaltLen == 0 || this.maxSaltLen < this.minSaltLen)
                return plainTextBytes;
            byte[] salt = this.GenerateSalt();
            byte[] destinationArray = new byte[plainTextBytes.Length + salt.Length];
            Array.Copy((Array)salt, (Array)destinationArray, salt.Length);
            Array.Copy((Array)plainTextBytes, 0, (Array)destinationArray, salt.Length, plainTextBytes.Length);
            return destinationArray;
        }

        private byte[] GenerateSalt()
        {
            int length = this.minSaltLen != this.maxSaltLen ? this.GenerateRandomNumber(this.minSaltLen, this.maxSaltLen) : this.minSaltLen;
            byte[] data = new byte[length];
            new RNGCryptoServiceProvider().GetNonZeroBytes(data);
            data[0] = (byte)((int)data[0] & 252 | length & 3);
            data[1] = (byte)((int)data[1] & 243 | length & 12);
            data[2] = (byte)((int)data[2] & 207 | length & 48);
            data[3] = (byte)((int)data[3] & 63 | length & 192);
            return data;
        }

        private int GenerateRandomNumber(int minValue, int maxValue)
        {
            byte[] data = new byte[4];
            new RNGCryptoServiceProvider().GetBytes(data);
            return new Random(((int)data[0] & (int)sbyte.MaxValue) << 24 | (int)data[1] << 16 | (int)data[2] << 8 | (int)data[3]).Next(minValue, maxValue + 1);
        }

        public static void Main()
        {
            string Key = "HelL!oWoRL3ds";
            string IV = "HElL!o@wOrld!#@%$";

            string toEncrypt = "1234";
            string encryptedData, decryptedData;

            CryptoProvider crypto = new CryptoProvider(Key, IV);
            encryptedData = crypto.Encrypt(toEncrypt.Trim());
            decryptedData = crypto.Decrypt(encryptedData.Trim());

            Console.WriteLine("ENCRYPTED: " + encryptedData);
            Console.WriteLine("DECRYPTED: " + decryptedData);
        }
    }
}

Nodejs Code :nodejs Code (codesandbox.io):

import { deriveBytesFromPassword } from "./deriveBytesFromPassword";
const Rijndael = require("rijndael-js");

const dataToEncrypt = "1234";

const SECRET_KEY = "HelL!oWoRL3ds"; // 13 chars
const SECRET_IV = "HElL!o@wOrld!#@%$"; // 17 chars

const keySize = 256;
const hashAlgorithm = "SHA512";

// Use only the first 16 bytes of the IV
const rgbIV = Buffer.from(SECRET_IV, "ascii").slice(0, 16); // @ref https://stackoverflow.com/a/57147116/12278028
const rgbSalt = Buffer.from([]);

const derivedPasswordBytes = deriveBytesFromPassword(
  SECRET_KEY,
  rgbSalt,
  3,
  hashAlgorithm,
  keySize / 8
);

const dataToEncryptInBytes = Buffer.from(dataToEncrypt, "utf8");

const cipher = new Rijndael(derivedPasswordBytes, "cbc");
const encrypted = Buffer.from(cipher.encrypt(dataToEncryptInBytes, 16, rgbIV));

console.log(encrypted.toString("base64"));

// Use this if you only have the Base64 string
// Note: The Base64 string in Line 34 is from C#
// const decrypted = Buffer.from(
//   cipher.decrypt(Buffer.from("zAqv5w/gwT0sFYXZEx+Awg==", "base64"), 16, rgbIV)
// );

const decrypted = Buffer.from(cipher.decrypt(encrypted, 16, rgbIV));

console.log(decrypted.toString());

I'm converting Rijndael decryption from C# to NodeJS.

The Key (or Passphrase) used is 13 characters long. The IV used is 17 characters long.

Note: I have no control over the length choice

Below is the Rijndael decryption in C#

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
                    
public class Program
{
    public class CryptoProvider
    {
        private ICryptoTransform encryptor = (ICryptoTransform)null;
        private ICryptoTransform decryptor = (ICryptoTransform)null;
        private int minSaltLen = -1;
        private int maxSaltLen = -1;
        
        public CryptoProvider(string passPhrase, string initVector)
          : this(passPhrase, initVector, -1, -1, -1, (string)null, (string)null, 3)
        {
        }

        public CryptoProvider(
          string passPhrase,
          string initVector,
          int minSaltLen,
          int maxSaltLen,
          int keySize,
          string hashAlgorithm,
          string saltValue,
          int passwordIterations)
        {
            this.minSaltLen = 4; 
            this.maxSaltLen = 8;
            keySize = 256;
            hashAlgorithm = "SHA512";

            byte[] rgbIV = Encoding.ASCII.GetBytes(initVector);
            byte[] rgbSalt = new byte[0];
            byte[] bytes = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);

            RijndaelManaged rijndaelManaged = new RijndaelManaged();

            if (rgbIV.Length == 0)
                rijndaelManaged.Mode = CipherMode.ECB;
            else
                rijndaelManaged.Mode = CipherMode.CBC;

            this.encryptor = rijndaelManaged.CreateEncryptor(bytes, rgbIV);
            this.decryptor = rijndaelManaged.CreateDecryptor(bytes, rgbIV);
        }

        public string Decrypt(string cipherText) {
            return this.Decrypt(Convert.FromBase64String(cipherText));
        }
        
        public string Decrypt(byte[] cipherTextBytes) {
            return Encoding.UTF8.GetString(this.DecryptToBytes(cipherTextBytes));
        }

        public byte[] DecryptToBytes(string cipherText) {
            return this.DecryptToBytes(Convert.FromBase64String(cipherText));
        }

        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            int num = 0;
            int sourceIndex = 0;
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            byte[] numArray = new byte[cipherTextBytes.Length];
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.decryptor, CryptoStreamMode.Read);
                num = cryptoStream.Read(numArray, 0, numArray.Length);
                memoryStream.Close();
                cryptoStream.Close();
            }
            if (this.maxSaltLen > 0 && this.maxSaltLen >= this.minSaltLen)
                sourceIndex = (int)numArray[0] & 3 | (int)numArray[1] & 12 | (int)numArray[2] & 48 | (int)numArray[3] & 192;
            byte[] destinationArray = new byte[num - sourceIndex];
            Array.Copy((Array)numArray, sourceIndex, (Array)destinationArray, 0, num - sourceIndex);
            return destinationArray;
        }
    }
    
    public static void Main()
        {
            string Key = "";
            string IV = "";

            string encryptedUserData = "u7uENpFfpQhMXiTThL/ajA==";
            string decryptedUserData;

            CryptoProvider crypto = new CryptoProvider(Key, IV);
            decryptedUserData = crypto.Decrypt(encryptedUserData.Trim());

            Console.WriteLine(decryptedUserData);

        }
}

which for some reason, I can decrypt the string in dotnetfiddle, but not in Visual Studio (because it returns an error of 'Specified initialization vector (IV) does not match the block size for this algorithm. (Parameter 'rgbIV')'

Below is my attempt to convert in NodeJS using the rijndael-js library:

const Rijndael = require("rijndael-js");

const key = "";
const iv = "";

const cipher = new Rijndael(key, "cbc");

const ciphertext = "u7uENpFfpQhMXiTThL/ajA==";

const plaintext = Buffer.from(cipher.decrypt(ciphertext, 256, iv));

which returns an error of Unsupported key size: 104 bit

All errors point to the same thing: Invalid Key/IV lengths.

Would there be a work-around where I can force NodeJS to accept the Key and IV as valid lengths? Is there something I am missing, doing incorrectly, or misconfigured?


Edit:

I was able to find a PasswordDeriveBytes implementation for NodeJS and compared the results from C# and they are equal.

I updated my NodeJS implementation (see sandbox) and noticed a few things:

  1. All resulting ciphertexts are the same. I am guessing this stems from salts.
  2. I tried decrypting a ciphertext generated from C#, but there seems to be a few characters to the left of the resulting value.
    Example: C# Encrypted String: zAqv5w/gwT0sFYXZEx+Awg==, NodeJS Decrypted String: ���&��4423
  3. When I try to decrypt a ciphertext generated in NodeJS in C#, the C# compiler returns an error of System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.

Edit:

C# code (executable with .NET Framework 4.7.2):

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

namespace ProgramEncrypt
{
    public class CryptoProvider
    {
        private ICryptoTransform encryptor = (ICryptoTransform)null;
        private ICryptoTransform decryptor = (ICryptoTransform)null;
        private int minSaltLen = -1;
        private int maxSaltLen = -1;

        public CryptoProvider(string passPhrase, string initVector) : this(passPhrase, initVector, -1, -1, -1, (string)null, (string)null, 3) { }

        public CryptoProvider(
          string passPhrase,
          string initVector,
          int minSaltLen,
          int maxSaltLen,
          int keySize,
          string hashAlgorithm,
          string saltValue,
          int passwordIterations)
        {
            this.minSaltLen = 4;
            this.maxSaltLen = 8;
            keySize = 256;
            hashAlgorithm = "SHA512";

            byte[] rgbIV = Encoding.ASCII.GetBytes(initVector);
            byte[] rgbSalt = new byte[0];
            byte[] bytes = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);

            RijndaelManaged rijndaelManaged = new RijndaelManaged();

            if (rgbIV.Length == 0)
                rijndaelManaged.Mode = CipherMode.ECB;
            else
                rijndaelManaged.Mode = CipherMode.CBC;

            this.encryptor = rijndaelManaged.CreateEncryptor(bytes, rgbIV);
            this.decryptor = rijndaelManaged.CreateDecryptor(bytes, rgbIV);
        }

        public string Encrypt(string plainText) => this.Encrypt(Encoding.UTF8.GetBytes(plainText));

        public string Encrypt(byte[] plainTextBytes) => Convert.ToBase64String(this.EncryptToBytes(plainTextBytes));

        public byte[] EncryptToBytes(string plainText) => this.EncryptToBytes(Encoding.UTF8.GetBytes(plainText));

        public byte[] EncryptToBytes(byte[] plainTextBytes)
        {
            byte[] buffer = this.AddSalt(plainTextBytes);
            MemoryStream memoryStream = new MemoryStream();
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.encryptor, CryptoStreamMode.Write);
                cryptoStream.Write(buffer, 0, buffer.Length);
                cryptoStream.FlushFinalBlock();
                byte[] array = memoryStream.ToArray();
                memoryStream.Close();
                cryptoStream.Close();
                return array;
            }
        }

        public string Decrypt(string cipherText) => this.Decrypt(Convert.FromBase64String(cipherText));

        public string Decrypt(byte[] cipherTextBytes) => Encoding.UTF8.GetString(this.DecryptToBytes(cipherTextBytes));

        public byte[] DecryptToBytes(string cipherText) => this.DecryptToBytes(Convert.FromBase64String(cipherText));

        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            int num = 0;
            int sourceIndex = 0;
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            byte[] numArray = new byte[cipherTextBytes.Length];
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, this.decryptor, CryptoStreamMode.Read);
                num = cryptoStream.Read(numArray, 0, numArray.Length);
                memoryStream.Close();
                cryptoStream.Close();
            }
            if (this.maxSaltLen > 0 && this.maxSaltLen >= this.minSaltLen)
                sourceIndex = (int)numArray[0] & 3 | (int)numArray[1] & 12 | (int)numArray[2] & 48 | (int)numArray[3] & 192;
            byte[] destinationArray = new byte[num - sourceIndex];
            Array.Copy((Array)numArray, sourceIndex, (Array)destinationArray, 0, num - sourceIndex);
            return destinationArray;
        }

        private byte[] AddSalt(byte[] plainTextBytes)
        {
            if (this.maxSaltLen == 0 || this.maxSaltLen < this.minSaltLen)
                return plainTextBytes;
            byte[] salt = this.GenerateSalt();
            byte[] destinationArray = new byte[plainTextBytes.Length + salt.Length];
            Array.Copy((Array)salt, (Array)destinationArray, salt.Length);
            Array.Copy((Array)plainTextBytes, 0, (Array)destinationArray, salt.Length, plainTextBytes.Length);
            return destinationArray;
        }

        private byte[] GenerateSalt()
        {
            int length = this.minSaltLen != this.maxSaltLen ? this.GenerateRandomNumber(this.minSaltLen, this.maxSaltLen) : this.minSaltLen;
            byte[] data = new byte[length];
            new RNGCryptoServiceProvider().GetNonZeroBytes(data);
            data[0] = (byte)((int)data[0] & 252 | length & 3);
            data[1] = (byte)((int)data[1] & 243 | length & 12);
            data[2] = (byte)((int)data[2] & 207 | length & 48);
            data[3] = (byte)((int)data[3] & 63 | length & 192);
            return data;
        }

        private int GenerateRandomNumber(int minValue, int maxValue)
        {
            byte[] data = new byte[4];
            new RNGCryptoServiceProvider().GetBytes(data);
            return new Random(((int)data[0] & (int)sbyte.MaxValue) << 24 | (int)data[1] << 16 | (int)data[2] << 8 | (int)data[3]).Next(minValue, maxValue + 1);
        }

        public static void Main()
        {
            string Key = "HelL!oWoRL3ds";
            string IV = "HElL!o@wOrld!#@%
quot;;

            string toEncrypt = "1234";
            string encryptedData, decryptedData;

            CryptoProvider crypto = new CryptoProvider(Key, IV);
            encryptedData = crypto.Encrypt(toEncrypt.Trim());
            decryptedData = crypto.Decrypt(encryptedData.Trim());

            Console.WriteLine("ENCRYPTED: " + encryptedData);
            Console.WriteLine("DECRYPTED: " + decryptedData);
        }
    }
}

NodeJS code (codesandbox.io):

import { deriveBytesFromPassword } from "./deriveBytesFromPassword";
const Rijndael = require("rijndael-js");

const dataToEncrypt = "1234";

const SECRET_KEY = "HelL!oWoRL3ds"; // 13 chars
const SECRET_IV = "HElL!o@wOrld!#@%
quot;; // 17 chars

const keySize = 256;
const hashAlgorithm = "SHA512";

// Use only the first 16 bytes of the IV
const rgbIV = Buffer.from(SECRET_IV, "ascii").slice(0, 16); // @ref https://stackoverflow.com/a/57147116/12278028
const rgbSalt = Buffer.from([]);

const derivedPasswordBytes = deriveBytesFromPassword(
  SECRET_KEY,
  rgbSalt,
  3,
  hashAlgorithm,
  keySize / 8
);

const dataToEncryptInBytes = Buffer.from(dataToEncrypt, "utf8");

const cipher = new Rijndael(derivedPasswordBytes, "cbc");
const encrypted = Buffer.from(cipher.encrypt(dataToEncryptInBytes, 16, rgbIV));

console.log(encrypted.toString("base64"));

// Use this if you only have the Base64 string
// Note: The Base64 string in Line 34 is from C#
// const decrypted = Buffer.from(
//   cipher.decrypt(Buffer.from("zAqv5w/gwT0sFYXZEx+Awg==", "base64"), 16, rgbIV)
// );

const decrypted = Buffer.from(cipher.decrypt(encrypted, 16, rgbIV));

console.log(decrypted.toString());

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

在梵高的星空下 2025-01-30 03:25:21

与C#代码兼容的基于沙盒代码的可能的NodeJS实现是:

const crypto = require("crypto");
const Rijndael = require("rijndael-js");
const pkcs7 = require('pkcs7-padding');

const SECRET_KEY = "HelL!oWoRL3ds"; // 13 chars
const SECRET_IV = "HElL!o@wOrld!#@%$"; // 17 chars
const rgbIV = Buffer.from(SECRET_IV, "ascii").slice(0, 16); 
const rgbSalt = Buffer.from([]);

const keySize = 256;
const hashAlgorithm = "SHA512";

const minSaltLen = 4;
const maxSaltLen = 8;

function encrypt(plaintextStr) {
  var derivedPasswordBytes = deriveBytesFromPassword(SECRET_KEY, rgbSalt, 3, hashAlgorithm, keySize/8);  
  var cipher = new Rijndael(derivedPasswordBytes, "cbc");
  var plaintext = Buffer.from(plaintextStr, "utf8");
  var salt = generateSalt();
  var saltPlaintext = Buffer.concat([salt, plaintext])
  var saltPlaintextPadded = pkcs7.pad(saltPlaintext, 16)
  var ciphertext = Buffer.from(cipher.encrypt(saltPlaintextPadded, 128, rgbIV));
  return ciphertext.toString("base64");
}

function decrypt(ciphertextB64) {
  var derivedPasswordBytes = deriveBytesFromPassword(SECRET_KEY, rgbSalt, 3, hashAlgorithm, keySize/8);  
  var cipher = new Rijndael(derivedPasswordBytes, "cbc");
  var ciphertext = Buffer.from(ciphertextB64, 'base64');
  var saltPlaintextPadded = Buffer.from(cipher.decrypt(ciphertext, 128, rgbIV));
  var sourceIndex = saltPlaintextPadded[0] & 3 | saltPlaintextPadded[1] & 12 | saltPlaintextPadded[2] & 48 | saltPlaintextPadded[3] & 192
  var plaintextPadded = saltPlaintextPadded.subarray(sourceIndex)
  var plaintext = pkcs7.unpad(plaintextPadded)
  return plaintext;
}

function generateSalt() {
  var length =  minSaltLen !=  maxSaltLen ?  crypto.randomInt(minSaltLen,  maxSaltLen + 1) :  minSaltLen;
  var data = crypto.randomBytes(length);
  data[0] = data[0] & 252 | length & 3;
  data[1] = data[1] & 243 | length & 12;
  data[2] = data[2] & 207 | length & 48;
  data[3] = data[3] & 63 | length & 192;
  return data;
}

var plaintext = "1234";
var ciphertextB64 = encrypt(plaintext);
var plaintext = decrypt(ciphertextB64);
console.log(ciphertextB64);
console.log(plaintext.toString('hex'))

使用

用此代码生成的密文可以用C#代码解密,反之亦然,可以用C#代码生成的密文可以用此代码解密。


说明:

  • 链接的C#代码可以在.NET框架下处理17个字节IV(测试为4.7.2)。但是,仅考虑前16个字节。使用添加 rijndaelManaged.iv = rgbiv (如MS示例中)抛出了例外。在.NET核心(测试3.0+)下,总是会抛出一个例外。这表明在.NET框架中处理IV太大,更可能是一个错误。无论如何,在nodejs代码中也只需要考虑iv的前16个字节。
  • C#代码使用专有的密钥推导 passwordDeriveBytes 。必须在nodejs代码中应用相同的密钥推导。在上面的代码中,使用OP链接的实现。
  • 涉及 rijndael-js 应用零填充物,但C#代码使用PKCS#7填充。因此,在nodejs代码中,必须在加密之前用PKCS#7填充明文(或盐和明文的串联)(这满足了长度标准,并且不再应用零填充物)。因此,解密后必须去除填充物。可能的库是 pkcs7-padding 。另外,可以使用另一个库,而不是rijndael-js,默认情况下应用了PKCS#7填充。
  • C#代码使用两种盐:一个是 empty (!) rgbsalt ,该是在密钥推导中应用的。另一个是A second 盐,该盐是根据加密过程中的长度和内容随机生成的,被固定在纯文本上,并包含有关盐长度的信息,该信息是在解密过程中确定的。必须在nodejs代码中实现此逻辑,以使两个代码兼容。
  • GeneratatorAndomNumber()方法无法移植,因为其结果取决于 tandom() 实现(顺便说一句,这不是csprng)。该方法应该生成一个随机整数。为此目的>使用。对于 rngcryptoserviceProvider#getNonzerobytes() 应用。此nodejs函数还允许0x00字节,如果需要,可以优化。

安全性:

  • 专有密钥推导 passwordderiveBytes 被弃用和不安全。相反, 应在c#代码和 pbkdf2 在nodejs代码中。
  • 密钥推导中缺少的盐是不安全的,并且允许通过彩虹表攻击。取而代之的是,应随机生成每个加密的足够大小(至少8个字节)的盐。这种盐不是秘密的,因此通常与密文相连。
  • C#实现使用静态IV,这也是不安全的。尽管随机 second 盐为相同的明文和相同的IV提供了不同的密文,但应应用最佳实践,而不是用户定义的构造。一种经过验证的方法是一种随机生成的静脉注射,类似于用于键推导的盐(为每个加密随机生成,与密文串联)。

A possible NodeJS implementation based on your sandbox code that is compatible with the C# code is:

const crypto = require("crypto");
const Rijndael = require("rijndael-js");
const pkcs7 = require('pkcs7-padding');

const SECRET_KEY = "HelL!oWoRL3ds"; // 13 chars
const SECRET_IV = "HElL!o@wOrld!#@%
quot;; // 17 chars
const rgbIV = Buffer.from(SECRET_IV, "ascii").slice(0, 16); 
const rgbSalt = Buffer.from([]);

const keySize = 256;
const hashAlgorithm = "SHA512";

const minSaltLen = 4;
const maxSaltLen = 8;

function encrypt(plaintextStr) {
  var derivedPasswordBytes = deriveBytesFromPassword(SECRET_KEY, rgbSalt, 3, hashAlgorithm, keySize/8);  
  var cipher = new Rijndael(derivedPasswordBytes, "cbc");
  var plaintext = Buffer.from(plaintextStr, "utf8");
  var salt = generateSalt();
  var saltPlaintext = Buffer.concat([salt, plaintext])
  var saltPlaintextPadded = pkcs7.pad(saltPlaintext, 16)
  var ciphertext = Buffer.from(cipher.encrypt(saltPlaintextPadded, 128, rgbIV));
  return ciphertext.toString("base64");
}

function decrypt(ciphertextB64) {
  var derivedPasswordBytes = deriveBytesFromPassword(SECRET_KEY, rgbSalt, 3, hashAlgorithm, keySize/8);  
  var cipher = new Rijndael(derivedPasswordBytes, "cbc");
  var ciphertext = Buffer.from(ciphertextB64, 'base64');
  var saltPlaintextPadded = Buffer.from(cipher.decrypt(ciphertext, 128, rgbIV));
  var sourceIndex = saltPlaintextPadded[0] & 3 | saltPlaintextPadded[1] & 12 | saltPlaintextPadded[2] & 48 | saltPlaintextPadded[3] & 192
  var plaintextPadded = saltPlaintextPadded.subarray(sourceIndex)
  var plaintext = pkcs7.unpad(plaintextPadded)
  return plaintext;
}

function generateSalt() {
  var length =  minSaltLen !=  maxSaltLen ?  crypto.randomInt(minSaltLen,  maxSaltLen + 1) :  minSaltLen;
  var data = crypto.randomBytes(length);
  data[0] = data[0] & 252 | length & 3;
  data[1] = data[1] & 243 | length & 12;
  data[2] = data[2] & 207 | length & 48;
  data[3] = data[3] & 63 | length & 192;
  return data;
}

var plaintext = "1234";
var ciphertextB64 = encrypt(plaintext);
var plaintext = decrypt(ciphertextB64);
console.log(ciphertextB64);
console.log(plaintext.toString('hex'))

using the key derivation from the linked post.

Ciphertexts generated with this code can be decrypted with the C# code, and vice versa, ciphertexts generated with the C# code can be decrypted with this code.


Explanation:

  • The linked C# code can process a 17 bytes IV under .NET Framework (tested for 4.7.2). However, only the first 16 bytes are taken into account. With the addition rijndaelManaged.IV = rgbIV (as in the MS examples) an exception is thrown. Under .NET Core (tested for 3.0+) an exception is always thrown. This indicates that processing an IV in the .NET Framework that is too large, is more likely a bug. Anyway, in the NodeJS code also only the first 16 bytes of the IV have to be considered.
  • The C# code uses the proprietary key derivation PasswordDeriveBytes. The same key derivation must be applied in the NodeJS code. In the code above, the implementation linked by the OP is used.
  • The library involved rijndael-js applies Zero padding, but the C# code uses PKCS#7 padding. Therefore, in the NodeJS code, the plaintext (or concatenation of salt and plaintext) must be padded with PKCS#7 before encryption (this satisfies the length criterion and Zero padding is no longer applied). Accordingly, the padding must be removed after decryption. A possible library is pkcs7-padding. Alternatively, instead of rijndael-js, another library could be used which applies PKCS#7 padding by default.
  • The C# code uses two salts: One is the empty (!) rgbSalt, which is applied in the key derivation. The other is a second salt, which is randomly generated with respect to both length and content during encryption, is prepended to the plaintext, and contains the information about the salt length, which is determined during decryption. This logic must be implemented in the NodeJS code for both codes to be compatible.
  • The GenerateRandomNumber() method cannot be ported because its result depends on the internal details of the Random() implementation (which, by the way, is not a CSPRNG). The method is supposed to generate a random integer. For this purpose crypto.randomInt() is used. For RNGCryptoServiceProvider#GetNonZeroBytes() create.RandomBytes() is applied. This NodeJS function also allows 0x00 bytes, which could be optimized if needed.

Security:

  • The proprietary key derivation PasswordDeriveBytes is deprecated and insecure. Instead, Rfc2898DeriveBytes should be used in the C# code and PBKDF2 in the NodeJS code.
  • The missing salt in the key derivation is insecure and allows attacks e.g. via rainbow tables. Instead, a salt of sufficient size (at least 8 bytes) should be randomly generated for each encryption. This salt is not secret and is therefore usually concatenated with the ciphertext.
  • The C# implementation uses a static IV, which is insecure as well. Although the random second salt provides a different ciphertext for identical plaintexts and identical IVs, a best practice should be applied instead of a user defined construct. A proven way is a randomly generated IV, analogous to the salt used for key derivation (randomly generated for each encryption, concatenated with the ciphertext).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文