目标:使用 CryptoJS (Javascript) 复制 Java-AES-CBC 加密/解密
为什么?: 使用 CryptoJS 创建加密字符串(Base64 或 HEX)并使用 Java 进行解码(不同的服务器环境)
问题:我无法让 CryptoJS 部分生成与 Java 相同的 Base64(或 HEX)编码字符串。 因此,在当前的代码状态下,无法将加密/解密交给不同的各方。
我的 Java 代码输出如下:
SECRET_KEY: 431.396
SALT: d413321c765f563c8288ce281ae6808d
originalString: test123_XXX
encryptedString: suBMmUzhQMtuAboORtvr6g==
encryptedStringHex: 7375424d6d557a68514d747541626f4f5274767236673d3d
decryptedString: test123_XXX
我的 Javascript 代码输出以下内容
[Log] SECRET_KEY: 431.396
[Log] SALT: d413321c765f563c8288ce281ae6808d
[Log] originalString: test123_XXX
[Log] Encrypted: l+R6aYgrQI2+RAIH+X/iJw==
[Log] encryptedHex: 97E47A69882B408DBE440207F97FE227
[Log] Decrypted: test123_XXX
这是我正在使用的完整Java代码:
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.security.*;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.KeySpec;
public class AES {
public static String encrypt(String strToEncrypt, String SECRET_KEY, String SALT) {
try {
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
System.out.println("Error while encrypting: " + e.toString());
}
return null;
}
public static String decrypt(String strToDecrypt, String SECRET_KEY, String SALT) {
try {
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
} catch (Exception e) {
System.out.println("Error while decrypting: " + e.toString());
}
return null;
}
///
public static void main(String args[]) {
String originalString = "test123_XXX";
String SECRET_KEY = "431.396";
String SALT = "d413321c765f563c8288ce281ae6808d";
String encryptedString = AES.encrypt(originalString, SECRET_KEY, SALT);
String encryptedStringHex = Hex.encodeHexString(encryptedString.getBytes());
String decryptedString = AES.decrypt(encryptedString, SECRET_KEY, SALT);
System.out.println("SECRET_KEY: " + SECRET_KEY);
System.out.println("SALT: " + SALT);
System.out.println("originalString: " + originalString);
System.out.println("encryptedString: " + encryptedString);
System.out.println("encryptedStringHex: " + encryptedStringHex);
System.out.println("decryptedString: " + decryptedString);
}
}
这是我正在使用的完整Javascript代码:
var SECRET_KEY = "431.396";
var SALT = "d413321c765f563c8288ce281ae6808d";
var originalString = "test123_XXX";
var iv = '0000000000000000';
iv = CryptoJS.enc.Hex.parse(iv);
var iterations = 65536;
var keySize = 256;
function encrypt (msg) {
var key = CryptoJS.PBKDF2(SECRET_KEY, SALT, {
keySize: keySize/32,
iterations: iterations
});
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC,
hasher: CryptoJS.algo.SHA256
});
return encrypted;
}
function decrypt (encrypted) {
var key = CryptoJS.PBKDF2(SECRET_KEY, SALT, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC,
hasher: CryptoJS.algo.SHA256
});
return decrypted;
}
var encrypted = encrypt(originalString);
var encryptedHex = encrypted.ciphertext.toString(CryptoJS.enc.Hex).toUpperCase();
var decrypted = decrypt(encrypted);
console.log("SECRET_KEY: "+ SECRET_KEY);
console.log("SALT: "+ SALT);
console.log("originalString: "+ originalString);
console.log("Encrypted: "+ encrypted);
console.log("encryptedHex: "+ encryptedHex);
console.log("Decrypted: "+ decrypted.toString(CryptoJS.enc.Utf8) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/aes.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/enc-hex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/md5.min.js"></script>
哈希算法是密钥导出函数的一部分。因此,在 CryptoJS 代码中,哈希算法必须在密钥派生函数中指定,而不是在
encrypt()
/decrypt()
调用中指定。通过此更改,CryptoJS 代码生成与 Java 代码相同的密文(比较 Base64 编码的密文):
var SECRET_KEY = "431.396";
var SALT = "d413321c765f563c8288ce281ae6808d";
var originalString = "test123_XXX";
var iv = '00000000000000000000000000000000'; // 16 bytes correspons to 32 hex digits
iv = CryptoJS.enc.Hex.parse(iv);
var iterations = 65536;
var keySize = 256;
function encrypt (msg) {
var key = CryptoJS.PBKDF2(SECRET_KEY, SALT, {
keySize: keySize/32,
iterations: iterations,
hasher: CryptoJS.algo.SHA256 // the hash algorithm is part of the key derivation
});
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7, // default
mode: CryptoJS.mode.CBC // default
});
return encrypted;
}
function decrypt (encrypted) {
var key = CryptoJS.PBKDF2(SECRET_KEY, SALT, {
keySize: keySize/32,
iterations: iterations,
hasher: CryptoJS.algo.SHA256 // the hash algorithm is part of the key derivation
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7, // default
mode: CryptoJS.mode.CBC // default
});
return decrypted;
}
var encrypted = encrypt(originalString);
var encryptedHex = encrypted.ciphertext.toString(CryptoJS.enc.Hex).toUpperCase();
var decrypted = decrypt(encrypted);
console.log("SECRET_KEY: "+ SECRET_KEY);
console.log("SALT: "+ SALT);
console.log("originalString: "+ originalString);
console.log("Encrypted: "+ encrypted);
console.log("encryptedHex: "+ encryptedHex);
console.log("Decrypted: "+ decrypted.toString(CryptoJS.enc.Utf8) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
十六进制编码的密文仍然不同,因为在 Java 代码中 Base64 编码的密文是十六进制编码的。这种双重编码是不必要的,应该避免。例如。对 Java 代码进行以下更改:
String encryptedStringHex = Hex.encodeHexString(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.US_ASCII)));
这些值也匹配(大写/小写除外)。
另请注意,AES 的 IV 大小为 16 个字节,这意味着十六进制编码值由 32 个十六进制数字组成。在 CryptoJS 代码中,您仅使用 16 个十六进制数字,这仅起作用,因为应用了零 IV,并且 CryptoJS 隐式地将太短的 IV 用 0x00 值填充到正确的长度。
关于安全性:静态盐和IV是漏洞。正确的方法是在加密过程中生成随机 IV 和 salt,并将两者与密文一起传递到解密方,通常是串联的(IV 和 salt 不是秘密)。