加密JSuses https://github.com/CryptoStore/crypto-js/tree/3.1.2/src/cipher-core.js用于密钥派生的非标准化 OpenSSL KDF (EvpKDF https://github.com/CryptoStore/crypto-js/tree/3.1.2/src/evpkdf.js) with MD5作为哈希算法和1迭代。 IV 也是从密码派生的,这意味着在 Java 端解密只需要实际的密文、密码和盐。
换句话说,在CryptoJS的密码模式下,PBKDF2不用于密钥推导。默认情况下,AES-256 用于带有 PKCS5 填充的 CBC 模式(这是与 PKCS7 填充相同 https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding)。请记住,您可能需要JCE 无限强度管辖权政策文件 http://www.oracle.com/technetwork/java/javase/downloads/index.html。也可以看看为什么使用超过一定长度的密钥进行加密会受到限制? https://crypto.stackexchange.com/questions/20524/why-there-are-limitations-on-using-encryption-with-keys-beyond-certain-length
以下代码在 Java 中重新创建 KDF (keySize
and ivSize
对于 AES-256,分别为 8 和 4,来自)。
public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
int targetKeySize = keySize + ivSize;
byte[] derivedBytes = new byte[targetKeySize * 4];
int numberOfDerivedWords = 0;
byte[] block = null;
MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
while (numberOfDerivedWords < targetKeySize) {
if (block != null) {
hasher.update(block);
}
hasher.update(password);
block = hasher.digest(salt);
hasher.reset();
// Iterations
for (int i = 1; i < iterations; i++) {
block = hasher.digest(block);
hasher.reset();
}
System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
numberOfDerivedWords += block.length/4;
}
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
return derivedBytes; // key + iv
}
以下是完整的类供参考:
public class RecreateEVPkdfFromCryptoJS {
public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException {
String msg = "hello";
String password = "mypassword";
String ivHex = "aab7d6aca0cc6ffc18f9f5909753aa5f";
int keySize = 8; // 8 words = 256-bit
int ivSize = 4; // 4 words = 128-bit
String keyHex = "844a86d27d96acf3147aa460f535e20e989d1f8b5d79c0403b4a0f34cebb093b";
String saltHex = "ca35168ed6b82778";
String openSslFormattedCipherTextString = "U2FsdGVkX1/KNRaO1rgneK9S3zuYaYZcdXmVKJGqVqk=";
String cipherTextHex = "af52df3b9869865c7579952891aa56a9";
String padding = "PKCS5Padding";
byte[] key = hexStringToByteArray(keyHex);
byte[] iv = hexStringToByteArray(ivHex);
byte[] salt = hexStringToByteArray(saltHex);
byte[] cipherText = hexStringToByteArray(cipherTextHex);
byte[] javaKey = new byte[keySize * 4];
byte[] javaIv = new byte[ivSize * 4];
evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv);
System.out.println(Arrays.equals(key, javaKey) + " " + Arrays.equals(iv, javaIv));
Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Must specify the mode explicitly as most JCE providers default to ECB mode!!
IvParameterSpec ivSpec = new IvParameterSpec(javaIv);
aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec);
byte[] byteMsg = aesCipherForEncryption.doFinal(cipherText);
System.out.println(Arrays.equals(byteMsg, msg.getBytes("UTF-8")));
}
public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
return evpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
}
public static byte[] evpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
int targetKeySize = keySize + ivSize;
byte[] derivedBytes = new byte[targetKeySize * 4];
int numberOfDerivedWords = 0;
byte[] block = null;
MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
while (numberOfDerivedWords < targetKeySize) {
if (block != null) {
hasher.update(block);
}
hasher.update(password);
block = hasher.digest(salt);
hasher.reset();
// Iterations
for (int i = 1; i < iterations; i++) {
block = hasher.digest(block);
hasher.reset();
}
System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
numberOfDerivedWords += block.length/4;
}
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
return derivedBytes; // key + iv
}
/**
* Copied from http://stackoverflow.com/a/140861
* */
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}
以及用于生成 Java 代码中的值的 JavaScript 代码:
var msg = "hello";
var password = "mypassword"; // must be present on the server
var encrypted = CryptoJS.AES.encrypt( msg, password );
var ivHex = encrypted.iv.toString();
var ivSize = encrypted.algorithm.ivSize; // same as the blockSize
var keySize = encrypted.algorithm.keySize;
var keyHex = encrypted.key.toString();
var saltHex = encrypted.salt.toString(); // must be sent as well
var openSslFormattedCipherTextString = encrypted.toString(); // not used
var cipherTextHex = encrypted.ciphertext.toString(); // must be sent