本文详细解析Node.js中AES加密结果与Java、C#不一致的原因及解决方法。通过对比示例,指出Node.js默认实现差异,并给出使用向量(IV)和密钥MD5哈希处理的具体代码方案,确保跨平台加解密数据一致。
Node.js AES加密与Java/C#不一致的原因
示例一:
近期遇到一个棘手问题:Node.js生成的AES加密结果与Java、C#不一致,导致无法解密。经过反复排查,最终通过查阅源码找到原因。网上有观点认为Node.js的AES实现与其他语言存在差异,这确实是一个常见痛点。关键在于Node.js的crypto模块使用方式。
代码如下:
var crypto = require('crypto');
var data = "156156165152165156156";
console.log('Original cleartext: ' + data);
var algorithm = 'aes-128-ecb';
var key = '78541561566';
var clearEncoding = 'utf8';
//var cipherEncoding = 'hex';
//If the next line is uncommented, the final cleartext is wrong.
var cipherEncoding = 'base64';
/*加密*/
var cipher = crypto.createCipher(algorithm, key);
var cipherChunks = [];
cipherChunks.push(cipher.update(data, clearEncoding, cipherEncoding));
cipherChunks.push(cipher.final(cipherEncoding));
console.log(cipherEncoding + ' ciphertext: ' + cipherChunks.join(''));
/*解密*/
var decipher = crypto.createDecipher(algorithm, key);
var plainChunks = [];
for (var i = 0;i < cipherChunks.length;i++) {
plainChunks.push(decipher.update(cipherChunks[i], cipherEncoding, clearEncoding));
}
plainChunks.push(decipher.final(clearEncoding));
console.log("UTF8 plaintext deciphered: " + plainChunks.join(''));上述代码运行无误,加密解密均成功,但结果与Java、C#生成的密文不同。这让人困惑。实际上,只需添加一个初始化向量(IV),即可使结果保持一致。由于相关资料较少,导致排查耗时较长。
正确代码是:代码如下:
var crypto = require('crypto');
var data = "156156165152165156156";
console.log('Original cleartext: ' + data);
var algorithm = 'aes-128-ecb';
var key = '78541561566';
var clearEncoding = 'utf8';
var iv = "";
//var cipherEncoding = 'hex';
//If the next line is uncommented, the final cleartext is wrong.
var cipherEncoding = 'base64';
var cipher = crypto.createCipheriv(algorithm, key,iv);
var cipherChunks = [];
cipherChunks.push(cipher.update(data, clearEncoding, cipherEncoding));
cipherChunks.push(cipher.final(cipherEncoding));
console.log(cipherEncoding + ' ciphertext: ' + cipherChunks.join(''));
var decipher = crypto.createDecipheriv(algorithm, key,iv);
var plainChunks = [];
for (var i = 0;i < cipherChunks.length;i++) {
plainChunks.push(decipher.update(cipherChunks[i], cipherEncoding, clearEncoding));
}
plainChunks.push(decipher.final(clearEncoding));
console.log("UTF8 plaintext deciphered: " + plainChunks.join(''));对比发现,加密结果现在与Java、C#一致。问题解决。
跨平台AES加密密钥处理方案
示例二:
在实际工作中,Node.js端通过AES加密,Android客户端使用Java解密,同时Node.js也需要解密Android端加密的内容。发现两端加密结果不一致,经查阅资料发现,Java端需要对密钥进行MD5加密处理。以下是AES ECB模式的代码示例,若使用CBC模式,同样需要对密钥进行MD5处理:
Node.js端代码:
代码如下:
/**
* aes加密
* @param data
* @param secretKey
*/
encryptUtils.aesEncrypt = function(data, secretKey) {
var cipher = crypto.createCipher('aes-128-ecb',secretKey);
return cipher.update(data,'utf8','hex') + cipher.final('hex');
}
/**
* aes解密
* @param data
* @param secretKey
* @returns {*}
*/
encryptUtils.aesDecrypt = function(data, secretKey) {
var cipher = crypto.createDecipher('aes-128-ecb',secretKey);
return cipher.update(data,'hex','utf8') + cipher.final('utf8');
} Java端代码:
代码如下:
package com.iofamily.util;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加密,与Nodejs 保持一致
* @author lmiky
* @date 2014-2-25
*/
public class AESForNodejs {
public static final String DEFAULT_CODING = "utf-8";
/**
* 解密
* @author lmiky
* @date 2014-2-25
* @param encrypted
* @param seed
* @return
* @throws Exception
*/
private static String decrypt(String encrypted, String seed) throws Exception {
byte[] keyb = seed.getBytes(DEFAULT_CODING);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(keyb);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Cipher dcipher = Cipher.getInstance("AES");
dcipher.init(Cipher.DECRYPT_MODE, skey);
byte[] clearbyte = dcipher.doFinal(toByte(encrypted));
return new String(clearbyte);
}
/**
* 加密
* @author lmiky
* @date 2014-2-25
* @param content
* @param key
* @return
* @throws Exception
*/
public static String encrypt(String content, String key) throws Exception {
byte[] input = content.getBytes(DEFAULT_CODING);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(key.getBytes(DEFAULT_CODING));
SecretKeySpec skc = new SecretKeySpec(thedigest, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
return parseByte2HexStr(cipherText);
}
/**
* 字符串转字节数组
* @author lmiky
* @date 2014-2-25
* @param hexString
* @return
*/
private static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
}
return result;
}
/**
* 字节转16进制数组
* @author lmiky
* @date 2014-2-25
* @param buf
* @return
*/
private static String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex);
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
System.out.println(AESForNodejs.encrypt("fsadfsdafsdafsdafsadfsadfsadf", "1234fghjnmlkiuhA"));
System.out.println(AESForNodejs.decrypt("5b8e85b7a86ad15a275a7cb61fe4c0606005e8741f68797718a3e90d74b5092a", "1234fghjnmlkiuhA"));
}
}