You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js Crypto与CryptoJS的Hmac SHA256 Base64结果不一致问题及统一方案

Why Do CryptoJS (Angular) and Node.js crypto Give Different Results? How to Align Them?

Great question! Let's break this down clearly:

Which Result Is "Correct"?

Neither result is inherently "wrong"—both are valid outputs from their respective encryption configurations. The difference comes down to mismatched encryption parameters between CryptoJS (Angular) and Node.js's crypto module. Common mismatches include:

  • How the raw private key is processed (CryptoJS defaults to deriving keys via PBKDF2 for string passwords, while Node.js crypto uses the string directly as a buffer unless told otherwise)
  • Initialization Vector (IV) generation/usage
  • Encryption mode or padding scheme differences

How to Get Matching Results in Angular (CryptoJS)

To align Angular's output with Node.js, you need to mirror every parameter used in the Node.js implementation. Below are two common scenarios with code examples:

Scenario 1: Node.js Uses Hashed Key + Random IV (Secure, Common Implementation)

Assume your Node.js code uses AES-256-CBC, hashes the raw key to get a valid 32-byte AES-256 key, generates a random 16-byte IV, and concatenates the IV with the ciphertext before encoding to base64:

// Node.js Implementation
const crypto = require('crypto');

function encryptNode(message, privateKey) {
  // Hash raw key to 32 bytes for AES-256
  const key = crypto.createHash('sha256').update(privateKey).digest();
  // Generate random 16-byte IV (AES block size)
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  
  let encrypted = cipher.update(message, 'utf8');
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  
  // Concatenate IV + ciphertext, then encode to base64
  return Buffer.concat([iv, encrypted]).toString('base64');
}

// Example output (varies per run due to random IV): nYu2PGqfRDWnHbT649q0gc+7DcIq8iwcwHAQQa5T2HY=
console.log(encryptNode('simple', '123456789'));

对应的Angular (CryptoJS) code that matches this behavior:

// Angular/CryptoJS Implementation
import * as CryptoJS from 'crypto-js';

function encryptAngular(message: string, privateKey: string): string {
  // Match Node.js's key hashing
  const key = CryptoJS.SHA256(privateKey);
  // Match random 16-byte IV generation
  const iv = CryptoJS.lib.WordArray.random(16);
  
  // Configure AES to match Node.js: CBC mode, PKCS7 padding (CryptoJS default)
  const encrypted = CryptoJS.AES.encrypt(message, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  
  // Concatenate IV + ciphertext, then encode to base64
  const combinedBytes = iv.concat(encrypted.ciphertext);
  return combinedBytes.toString(CryptoJS.enc.Base64);
}

// Output will match Node.js's result (varies per run due to random IV)
console.log(encryptAngular('simple', '123456789'));

Scenario 2: Align Node.js to CryptoJS's Default Behavior

If you want Node.js to match CryptoJS's default behavior (CryptoJS uses PBKDF2 to derive keys/IVs from string passwords), here's how to adjust Node.js:

// Node.js Implementation (aligned to CryptoJS defaults)
const crypto = require('crypto');

function encryptNodeAligned(message, password) {
  // CryptoJS defaults: PBKDF2 with 1000 iterations, SHA1, 256-bit key + 128-bit IV
  const salt = crypto.randomBytes(8);
  const keyIv = crypto.pbkdf2Sync(password, salt, 1000, 32 + 16, 'sha1');
  const key = keyIv.slice(0, 32);
  const iv = keyIv.slice(32, 48);
  
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  let encrypted = cipher.update(message, 'utf8');
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  
  // CryptoJS default output format: "Salted__" + salt + ciphertext
  const result = Buffer.concat([Buffer.from('Salted__'), salt, encrypted]).toString('base64');
  return result;
}

// Output will match CryptoJS's default AES.encrypt result
console.log(encryptNodeAligned('simple', '123456789'));

Critical Security Notes

  • Avoid ECB mode: It's insecure and doesn't use an IV. Always use CBC or GCM mode.
  • Random IVs are mandatory: If you use a random IV, you must transmit it with the ciphertext (e.g., prepend it) to decrypt successfully.
  • Use valid key lengths: AES-128 requires 16-byte keys, AES-256 requires 32-byte keys. Never use raw short strings as keys—derive them via hashing or PBKDF2.

内容的提问来源于stack exchange,提问作者Adam Adamski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:00:48