【问题标题】:AES 256 Nodejs Encryption and Decrypting it in C#.NetAES 256 Nodejs 加密和 C#.Net 中的解密
【发布时间】:2020-12-03 00:55:57
【问题描述】:

我尝试在 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, 0, 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();
});
});

【问题讨论】:

  • 请检查这里,同样的错误 - stackoverflow.com/questions/22466858/…
  • NodeJS 代码中缺少processKey 方法。如果发布一组完整的测试数据(密钥、IV、明文和密文)也会很有帮助。在任何情况下,NodeJS 代码都缺少 C# 代码中假定的 IV 和密文的连接。
  • 此外,NodeJS 代码中的密文是十六进制编码的,而 C# 代码需要 Base64 编码的密文。从NodeJS代码中我也不清楚为什么密文应该以qwerty开头,就像你的例子一样。
  • @Topaco 我故意用随机字母篡改密文,我尝试转换十六进制字符串而不是base64,现在输出为空。
  • IV 和密文的连接仍然缺失(见我的第一条评论)。在十六进制编码的情况下,这很简单:let ciphered = iv.toString(outputEncoding); ciphered += cipher.update(text, inputEncoding, outputEncoding); ciphered += cipher.final(outputEncoding);const outputEncoding = 'hex';。如果是 Base64 编码(最初用于 C# 代码),则改为:let ciphered = Buffer.concat([iv, cipher.update(text, inputEncoding), cipher.final()]).toString(outputEncoding);const outputEncoding = 'base64';

标签: c# node.js .net encryption cryptography


【解决方案1】:

发布的 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, key, 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没有贴出来,所以不能排除对key的进一步处理,所以这里不考虑。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-04
    • 2022-10-16
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 2013-08-16
    相关资源
    最近更新 更多