C语言tiny-aes-c与Node.js CryptoJS AES-CTR兼容问题排查
Hey there, let's dig into why your encryption/decryption isn't matching up between your C code (using tiny-aes-c) and Node.js's CryptoJS. This almost always boils down to subtle differences in how each library handles low-level CTR mode details—let's break down the most likely culprits and fixes:
1. Counter Byte Order (Endianness)
This is the #1 cause of AES-CTR incompatibility between libraries. Tiny-aes-c's default CTR implementation often uses little-endian for incrementing the counter, while CryptoJS follows the NIST standard and uses big-endian.
For example:
- Tiny-aes-c might increment the counter starting from the first byte (left to right)
- CryptoJS increments starting from the last byte (right to left, big-endian)
Fix: Modify your tiny-aes-c code to use big-endian counter increments. Locate the part of the CTR encryption loop where the counter is updated, and change it to:
// Replace any little-endian counter increment with this big-endian version for (int i = AES_BLOCK_SIZE - 1; i >= 0; i--) { if (++ctr[i]) { // Increment byte; break if no carryover break; } }
2. Verify IV and Key Consistency
Double-check that both sides are using identical IV and key bytes—no sneaky encoding differences allowed:
- If your IV is a hex string, make sure your C code parses it into the exact same byte array as CryptoJS (use
CryptoJS.enc.Hex.parse(ivHex)in Node.js) - For the 16-byte AES-128 key:
- If it's a hex string: Parse it to bytes in both C and Node.js
- If it's a plaintext string: Ensure both use the same encoding (e.g., UTF-8) to convert to bytes
3. Validate CryptoJS Decryption Setup
Make sure your Node.js code is correctly configured for CTR mode with no padding. A common mistake is passing the ciphertext directly as a string to CryptoJS.AES.decrypt—instead, explicitly pass the ciphertext bytes:
const CryptoJS = require("crypto-js"); // Replace with your actual key and IV (parsed to bytes) const key = CryptoJS.enc.Hex.parse("your-16-byte-key-in-hex"); const iv = CryptoJS.enc.Hex.parse("your-fixed-iv-in-hex"); // Parse the Base64 ciphertext to bytes const ciphertextBytes = CryptoJS.enc.Base64.parse("y6nVvIQRPA=="); const decrypted = CryptoJS.AES.decrypt( { ciphertext: ciphertextBytes }, key, { mode: CryptoJS.mode.CTR, iv: iv, padding: CryptoJS.pad.NoPadding } ); // Convert decrypted bytes to UTF-8 string console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Should output "message"
4. Check Encrypted Byte Order and Conversion
Verify that your C code's encrypted output bytes match what you're passing to CryptoJS:
- Take the raw encrypted byte array from your C code, convert it to hex (in order, first byte to last), and confirm it's exactly
cba9d5bc84113c - Double-check the Base64 conversion: The 7-byte array
[0xcb, 0xa9, 0xd5, 0xbc, 0x84, 0x11, 0x3c]should indeed convert toy6nVvIQRPA==—use a trusted byte-to-base64 tool to confirm.
5. Validate tiny-aes-c's CTR Implementation
Ensure tiny-aes-c is using standard CTR logic:
- The initial counter should be your full IV (16 bytes)
- Each block, encrypt the current counter to get the keystream, then XOR it with the plaintext
- Increment the entire counter (big-endian) after each block
If your tiny-aes-c code is splitting the IV into a nonce and a separate counter, that's non-standard—CryptoJS expects the full IV to be the initial counter.
Start with the counter endianness fix—this is almost certainly the issue. Once you adjust that, re-test your encryption and decryption, and verify each step's byte output matches between the two systems.
内容的提问来源于stack exchange,提问作者Shlomi Schwartz

