javascriptc#node.jsencryptionencryption-symmetric

Cannot match Rijdnael encryption between C# and Javascript / Node


I need to convert an Rijndael encryption function from C# to Node. But I cannot match the result, even with the same Key, IV, Mode and Block Size. What am I doing wrong?

C# MRE:

using System.Security.Cryptography;
byte[] encrypted;

using (Rijndael rijAlg = Rijndael.Create())
{
    rijAlg.Key = new byte[] {  63, 115,  38, 206,  45, 102, 229,  13,
    161, 196, 250, 133, 149,  70, 153,  33,
    218,  32, 135, 149, 150,  21, 143,  11,
    210, 115, 185, 163,  24,  70, 145, 141 };

    rijAlg.IV = new byte[] { 151, 121, 168, 83, 221, 99, 206, 230, 74, 190, 106, 212, 232, 117, 192, 37 };

    ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

    using (MemoryStream msEncrypt = new MemoryStream())
    {
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
            {
                swEncrypt.Write("1234");
            }
            encrypted = msEncrypt.ToArray();
        }
    }

    Console.WriteLine(rijAlg.BlockSize); //128
    Console.WriteLine(rijAlg.Mode); //CBC
    Console.WriteLine(rijAlg.Padding); //PKCS7
    Console.WriteLine(Convert.ToBase64String(encrypted)); //6d9SB6t9dktDJ+siSlFtOQ==
}

Node / Javascript MRE:

import Rijndael from "rijndael-js";

const key = Buffer.from([63, 115, 38, 206, 45, 102, 229, 13, 161, 196, 250, 133, 149, 70, 153, 33, 218, 32, 135, 149, 150, 21, 143, 11, 210, 115, 185, 163, 24, 70, 145, 141]);
const IV = Buffer.from([151, 121, 168, 83, 221, 99, 206, 230, 74, 190, 106, 212, 232, 117, 192, 37]);
const cipher = new Rijndael(key, "cbc");

const buffer = cipher.encrypt("1234", "128", IV);
console.log(Buffer.from(buffer).toString("base64")); //6ewzPyaxUgFX8IXW9iLJfw==

The result of encrypting "1234" in C# is "6d9SB6t9dktDJ+siSlFtOQ==" while in Node is "6ewzPyaxUgFX8IXW9iLJfw=="


Solution

  • Both codes use different paddings: PKCS#7 padding in the C# code and Zero padding in the NodeJS code. This is the reason for the different ciphertexts.
    In order for both codes to generate the same ciphertext, the same padding must be applied. Since PKCS#7 padding is more reliable than Zero padding, PKCS#7 padding should be used in both codes.

    rijndael-js does not seem to support PKCS#7 padding, only Zero padding, s. here. Although PKCS#7 padding can easily be implemented by yourself, an alternative to the rijndael-js library is the built-in crypto module of NodeJS, which supports PKCS#7 padding by default.

    Also note that rijndael-js implements the Rijndael algorithm, while in the C# code AES is used. This works because AES is a subset of Rijndael, s. here. On the other hand, AES and not Rijndael is the standard, and since the C# side uses AES, it is obvious to use a pure AES implementation on the NodeJS side as well, which applies to the crypto module of NodeJS.

    A possible implementation with the crypto module of NodeJS is:

    import crypto from 'crypto';
    const key = Buffer.from([63, 115, 38, 206, 45, 102, 229, 13, 161, 196, 250, 133, 149, 70, 153, 33, 218, 32, 135, 149, 150, 21, 143, 11, 210, 115, 185, 163, 24, 70, 145, 141]);
    const IV = Buffer.from([151, 121, 168, 83, 221, 99, 206, 230, 74, 190, 106, 212, 232, 117, 192, 37]);
    const cipher = crypto.createCipheriv('aes-256-cbc', key, IV);
    const ciphertext = Buffer.concat([cipher.update('1234', 'utf8'), cipher.final()]).toString('base64');
    console.log(ciphertext); // 6d9SB6t9dktDJ+siSlFtOQ==
    

    This implementation gives the same ciphertext as the C# code.