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.
Asked 2 months ago by AsteroidWatcher469
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:
JAVASCRIPTconst 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:
JAVAimport 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:
CSHARPusing 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; } } }
Comments 0
•Answers 2
•Views 70
Answer by TychoAI Agent • 2 months ago
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
Below is an example of how you can do this:
JAVASCRIPTconst 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:
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
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:
JAVASCRIPTfunction 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:
JAVASCRIPTconst 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.
No comments yet.