AES 256 Nodejs在C#.Net中进行加密和解密

问题描述

我尝试在C#中解密的数据是使用Nodejs中的AES-256算法加密的,代码如下。

const crypto = require('crypto');
const validator = require('validator');
const algorithm = 'aes256';
const inputEncoding = 'utf8';
const outputEncoding = 'hex';
const iv = crypto.randomBytes(16)

function encrypt(key,text) {
key = processKey(key);
let cipher = crypto.createCipheriv(algorithm,key,iv);
let ciphered = cipher.update(text,inputEncoding,outputEncoding);
ciphered += cipher.final(outputEncoding);
return ciphered;
}

现在,我获得了长度为32的加密数据(如“ 1234567304e07a5d2e93fbeefd0e417e”)和长度为32的密钥(如“ 123456673959499f9d37623168b2c977”)。

我正在尝试使用下面的c#代码对同一内容进行解密,并由于“要解密的数据长度无效”而收到错误消息。

public static string Decrypt(string combinedString,string keyString)
{
    string plainText;
    byte[] combinedData = StringToByteArray(combinedString);
    Aes aes = Aes.Create();
    aes.Key = Encoding.UTF8.GetBytes(keyString);
    byte[] iv = new byte[aes.BlockSize / 8];
    byte[] cipherText = new byte[combinedData.Length - iv.Length];
    Array.Copy(combinedData,iv,iv.Length);
    Array.Copy(combinedData,iv.Length,cipherText,cipherText.Length);
    aes.IV = iv;
    aes.Mode = CipherMode.CBC;
    ICryptoTransform decipher = aes.CreateDecryptor(aes.Key,aes.IV);

    using (MemoryStream ms = new MemoryStream(cipherText))
    {
        using (CryptoStream cs = new CryptoStream(ms,decipher,CryptoStreamMode.Read))
        {
            using (StreamReader sr = new StreamReader(cs))
            {
                plainText = sr.ReadToEnd();                 
            }
        }

        return plainText;
    }
}
public static byte[] StringToByteArray(string hex) {
return Enumerable.Range(0,hex.Length)
                 .Where(x => x % 2 == 0)
                 .Select(x => Convert.ToByte(hex.Substring(x,2),16))
                 .ToArray();
}

下面是Node.js中的解密代码,效果很好

const crypto = require('../functions/crypto');
const assert = require('assert');
const { v4: uuidv4 } = require('uuid');
describe('crypto module',function() {
it('should work',function(done) {
    const toHash = 'Octomate';
    const hashKey = uuidv4();

    const hash = crypto.encrypt(hashKey,toHash);
    const decrypted = crypto.decrypt(hashKey,hash);

    assert.strictEqual(toHash,decrypted);
    done();
});
});

解决方法

发布的NodeJS在CBC模式下使用AES-256执行加密。明文用UTF8编码,密文用十六进制编码。此外,生成随机IV并将其用于加密。由于方法processKey尚未发布,因此将不作进一步考虑,因此,以下NodeJS代码用于派生用于解密的C#代码:

const crypto = require('crypto');
const validator = require('validator');
const algorithm = 'aes256';
const inputEncoding = 'utf8';
const outputEncoding = 'hex';
const iv = crypto.randomBytes(16)

function encrypt(key,text) {
    //key = processKey(key);    // not posted
    let cipher = crypto.createCipheriv(algorithm,key,iv);
    let ciphered = cipher.update(text,inputEncoding,outputEncoding);
    ciphered += cipher.final(outputEncoding);
    return ciphered;
}

const key = '123456673959499f9d37623168b2c977';
const text = 'The quick brown fox jumps over the lazy dog'
const encrypted = encrypt(key,text);

console.log("IV (hex):         " + iv.toString('hex'));
console.log("Ciphertext (hex): " + encrypted);

使用发布的键123456673959499f9d37623168b2c977和纯文本The quick brown fox jumps over the lazy dog会产生以下输出:

IV (hex):         850bd88afd08c4ea14e75276277644f0
Ciphertext (hex): 5167ac87ebc79d5240255ff687c6bc8981c8791c353367a2e238a10e0983bf16e230ccf0511096f60c224b99927b3364

请注意,由于随机IV,每次加密都会生成不同的密文。

可以使用C#进行解密,如下所示:

string ciphertext = "5167ac87ebc79d5240255ff687c6bc8981c8791c353367a2e238a10e0983bf16e230ccf0511096f60c224b99927b3364";
string key = "123456673959499f9d37623168b2c977";
string iv = "850bd88afd08c4ea14e75276277644f0";
string decryptedText = Decrypt(ciphertext,iv);
Console.WriteLine("Decrypted text: " + decryptedText);

使用

public static string Decrypt(string ciphertextHex,string keyUtf8,string ivHex)
{
    byte[] ciphertext = StringToByteArray(ciphertextHex);
    byte[] iv = StringToByteArray(ivHex);

    string plaintext = "";
    using (Aes aes = Aes.Create())
    {
        aes.Key = Encoding.UTF8.GetBytes(keyUtf8);
        aes.IV = iv;
        aes.Mode = CipherMode.CBC;          // default
        aes.Padding = PaddingMode.PKCS7;    // default

        ICryptoTransform decipher = aes.CreateDecryptor(aes.Key,aes.IV);

        using (MemoryStream ms = new MemoryStream(ciphertext))
        {
            using (CryptoStream cs = new CryptoStream(ms,decipher,CryptoStreamMode.Read))
            {
                using (StreamReader sr = new StreamReader(cs,Encoding.UTF8)) // UTF8: default
                {
                    plaintext = sr.ReadToEnd();
                }
            }
        }
    }
    return plaintext;
}

// from https://stackoverflow.com/a/321404/9014097
public static byte[] StringToByteArray(string hex)
{
    return Enumerable.Range(0,hex.Length)
                     .Where(x => x % 2 == 0)
                     .Select(x => Convert.ToByte(hex.Substring(x,2),16))
                     .ToArray();
}

请注意以下几点:

  • 解密必须使用与加密相同的IV。由于IV的大小是已知的(与块大小相同),并且IV不是秘密的,因此通常将其放在字节级别的密文前面(未加密且没有分隔符),并且结果是Base64编码的。这被发送到接收器,Base64对其进行解码,然后分离接收到的数据。

    在发布的NodeJS代码中不会发生这种情况。然而,在您发布的C#代码 中,正是这种串联是预期的,因此将IV和密文分开。在我的评论中,我描述了NodeJS代码中用于连接IV和密文的更改,以便可以使用C#代码解密结果。

    由于NodeJS代码显然是主要代码,并且没有串联,因此在我的答案中 发布的C#代码也没有串联。但是,在这种情况下,必须通过IV。作为参数,否则,如上所述,就无法解密。

  • 不幸的是,发布的示例并不十分有用,因为您仅发布了密钥和密文(顺便说一下,因为它是十六进制编码的,所以只有16个字节长),但没有发布随机生成的IV。因此解密是不可能的。该示例也不能用于密文的比较,因为由于随机IV,每次都会生成不同的密文。

  • 由于NodeJS代码使用AES-256,因此密钥的大小必须为32个字节。因此,密钥可能是使用UTF8编码的。从这些值也可以进行十六进制编码,但这只会导致16字节的密钥。由于未发布方法processKey,因此不能排除对该密钥的进一步处理,因此在此不予考虑。

  • 不幸的是,发布的解密解密NodeJS代码也无济于事,因为未定义几种方法(例如crypto.encryptcrypto.decrypt),至少我看不到任何方法与发布的用于加密的NodeJS代码的有用关系。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...