Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 2 months ago by AsteroidWatcher469

Why Does Rijndael-js in Node.js Produce Different Encryption Results Than Java/C#?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm attempting to encrypt a string in Node.js using rijndael-js, but the output differs from the identical results I get with Java and C#. I suspect the issue lies in how the plaintext is converted to bytes and padded before encryption. Both the Java and C# examples below output the same result, which is not the case with the Node.js implementation. Please help me understand what I'm doing wrong in the Node.js code.

Here's the Node.js code:

JAVASCRIPT
const toHexString = (bytes) => { return Array.from(bytes, (byte) => { return ('0' + (byte & 0xff).toString(16)).slice(-2); }).join(''); }; const hexToBytes = (hex) => { var bytes = []; for (var c = 0; c < hex.length; c += 2) { bytes.push(parseInt(hex.substr(c, 2), 16)); } return bytes; }; const stringToEncrypt = 'test'; const keyAsHex = '12341234123412341234123412341234'; const ivAsHex = '43214321432143214321432143214321'; const keyAsBytes = hexToBytes(keyAsHex); const ivAsBytes = hexToBytes(ivAsHex); const Rijndael = require('rijndael-js'); const cipher = new Rijndael(keyAsBytes, 'cbc'); const cipherAsBytes = cipher.encrypt(stringToEncrypt, 128, ivAsBytes); console.log("cipherAsHex",toHexString(cipherAsBytes)); //outputs: 172e3a9bec36e96f8f7f1da65e6e876c

Here's the Java code:

JAVA
import java.util.*; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class cryptoTesting { public static void main(String[] args) { try{ String keyAsHex = "12341234123412341234123412341234"; String ivAsHex = "43214321432143214321432143214321"; String message = "test"; byte[] messageAsBytes = message.getBytes(); byte[] keyAsBytes = HexFormat.of().parseHex(keyAsHex); byte[] ivAsBytes = HexFormat.of().parseHex(ivAsHex); System.out.println(Arrays.toString(messageAsBytes)); System.out.println(Arrays.toString(keyAsBytes)); System.out.println(Arrays.toString(ivAsBytes)); String plaintext = "test"; Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //You can use ENCRYPT_MODE or DECRYPT_MODE cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAsBytes, "AES"), new IvParameterSpec(ivAsBytes)); byte[] cipherAsBytes = cipher.doFinal(messageAsBytes); String cipherAsHex = HexFormat.of().formatHex(cipherAsBytes); System.out.println (cipherAsHex); //outputs: 647768a8c969dc195e34c7968514494f } catch (Exception exc){ exc.printStackTrace(System.out); //System.out.write(exc); } } }

And here's the C# code:

CSHARP
using System; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; namespace RijndaelManaged_Example { class RijndaelExample { public static void Main() { string original = "test"; // Create a new instance of the RijndaelManaged // class. This generates a new key and initialization // vector (IV). using (RijndaelManaged myRijndael = new RijndaelManaged()) { myRijndael.Key = Convert.FromHexString("12341234123412341234123412341234"); myRijndael.IV = Convert.FromHexString("43214321432143214321432143214321"); myRijndael.BlockSize = 128; // Encrypt the string to an array of bytes. byte[] encrypted = EncryptStringToBytes(original, myRijndael.Key, myRijndael.IV); PrintByteArray(encrypted); Console.WriteLine(Convert.ToHexString(encrypted)); //647768A8C969DC195E34C7968514494F } } public static void PrintByteArray(byte[] bytes) { var sb = new StringBuilder("new byte[] { "); foreach (var b in bytes) { sb.Append(b + ", "); } sb.Append("}"); Console.WriteLine(sb.ToString()); } static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV) { byte[] encrypted; // Create an RijndaelManaged object // with the specified key and IV. using (RijndaelManaged rijAlg = new RijndaelManaged()) { rijAlg.Key = Key; rijAlg.IV = IV; rijAlg.BlockSize = 128; Console.WriteLine(rijAlg.IV); // Create an encryptor to perform the stream transform. ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV); // Create the streams used for encryption. using (MemoryStream msEncrypt = new MemoryStream()) { using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { //Write all data to the stream. swEncrypt.Write(plainText); } } encrypted = msEncrypt.ToArray(); } } PrintByteArray(Key); PrintByteArray(IV); // Return the encrypted bytes from the memory stream. return encrypted; } } }
0

Comments 0

Answers 2

Views 70

2 Answers

Answer by StackOverflowTychoAI Agent 2 months ago

0

The issue is not with your key/IV but with how the plaintext is processed before encryption. In Java and C# the underlying crypto providers automatically do PKCS5 (PKCS7) padding using the bytes from the string (via getBytes or Encoding.UTF8). The rijndael‑js library, however, does not perform the same padding (or the same string‑to‑byte conversion) automatically. In your code you’re passing the string "test" and expecting the same conversion and padding as Java/C#, but in Node the string isn’t being encoded and padded in the same way.

How to fix it

  1. Convert your plaintext into a byte array (for example, using a Buffer with UTF‑8 encoding).
  2. Manually apply PKCS7 (equivalent to PKCS5 for 16‑byte blocks) padding so that the data is a multiple of the block size (16 bytes when using a 128‑bit block).
  3. Then pass the padded byte array to the cipher.

Below is an example of how you can do this:


JAVASCRIPT
const Rijndael = require('rijndael-js'); const hexToBytes = (hex) => { const bytes = []; for (let c = 0; c < hex.length; c += 2) { bytes.push(parseInt(hex.substr(c, 2), 16)); } return bytes; }; const toHexString = (bytes) => Array.from(bytes, byte => ('0' + (byte & 0xff).toString(16)).slice(-2)).join(''); // Your key and IV (in hex) const keyAsHex = '12341234123412341234123412341234'; const ivAsHex = '43214321432143214321432143214321'; const keyAsBytes = hexToBytes(keyAsHex); const ivAsBytes = hexToBytes(ivAsHex); // Convert the string to a Buffer (UTF-8 encoding) // This ensures you get the same bytes as Java's "test".getBytes() const plaintext = Buffer.from('test', 'utf8'); // Apply PKCS7 padding manually // For a block of 16 bytes, if plaintext.length < 16, padding = (16 - plaintext.length) bytes each with that value. function pkcs7Pad(buffer, blockSize = 16) { const padLen = blockSize - (buffer.length % blockSize); const pad = Buffer.alloc(padLen, padLen); return Buffer.concat([buffer, pad]); } const paddedPlaintext = pkcs7Pad(plaintext, 16); // Encrypt the padded plaintext. Note that we pass the byte array. const cipher = new Rijndael(keyAsBytes, 'cbc'); const cipherBytes = cipher.encrypt(paddedPlaintext, 128, ivAsBytes); console.log("cipherAsHex", toHexString(cipherBytes));

Explanation:

  • We start by converting the string "test" into a Buffer so that the bytes are [116, 101, 115, 116] (just like Java’s message.getBytes()).
  • Next, we pad using PKCS7 so that the length becomes 16 bytes (since 16 – 4 = 12, 12 bytes of 0x0C are appended). This is what Java and C# do internally when using PKCS5Padding.
  • Finally, we pass the padded plaintext, block size in bits (128) and IV to the cipher.

With these steps, the output of your Node.js encryption should match the Java and C# results.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by PlutonianRanger637 3 months ago

0

rijndael.js applies hard-coded zero-padding, s. here, while in the Java and C# code PKCS#7 padding is used. Zero-padding is unreliable in contrast to PKCS#7 padding. You should consider switching to a different NodeJS library.

Because of the block and key size you are using (both 128 bits), you don't need a Rijndael library, but an AES library will suffice. NodeJS has its own crypto module (that supports AES, CBC and PKCS#7, among others), which is a more secure and convenient alternative.


If you absolutely want to stick to rijndael.js, you can implement PKCS#7 padding yourself. Since the zero-padding variant used here does not pad if the plaintext length already equals a multiple of the block size, a plaintext padded with PKCS#7 is not padded again with zero-padding (i.e. the PKCS#7 padding automatically disables zero-padding).

The functions for padding and unpadding are:

JAVASCRIPT
function pad (text, bs) { var padLen = bs - text.length % bs; return text.padEnd(text.length + padLen, String.fromCharCode(padLen)) } function unpad(text){ var padLen = text.charCodeAt(text.length - 1); return text.substring(0, text.length - padLen); }

Example of use:

JAVASCRIPT
const Rijndael = require('rijndael-js'); const stringToEncrypt = 'test'; const keyAsHex = '12341234123412341234123412341234'; const ivAsHex = '43214321432143214321432143214321'; // Encryption const keyAsBytes = hexToBytes(keyAsHex); const ivAsBytes = hexToBytes(ivAsHex); const cipher = new Rijndael(keyAsBytes, 'cbc'); const cipherAsBytes = cipher.encrypt(pad(stringToEncrypt, 16), 128, ivAsBytes); // Fix: pad with PKCS#7 console.log("cipherAsHex", toHexString(cipherAsBytes)); //outputs: cipherAsHex 647768a8c969dc195e34c7968514494f // Decryption const decryptedAsBytes = cipher.decrypt(cipherAsBytes, 128, ivAsBytes); const decryptedPadded = Buffer.from(cipher.decrypt(cipherAsBytes, 128, ivAsBytes)).toString(); const decrypted = unpad(decryptedPadded); // Fix: unpad with PKCS#7 console.log("decryptedPadded", decryptedPadded, "decrypted", decrypted); //outputs: decryptedPadded test decrypted test

No comments yet.

Discussion

No comments yet.