背景 我正在处理的应用程序应该脱机工作。我应该使用密码作为Java服务器端的密钥加密一些文本数据。加密数据传递到HTML5页面,在客户端使用crypto-js库,应解密服务器加密数据。
我的问题: 为了加密我的消息,客户端可以使用crypt-js(使用用户输入的密码)对其进行解密,我需要知道加密消息时crypto-js所期望的确切步骤。
我需要知道的事情: 我有以下加密代码,使用crypto-js在客户端加密消息。
var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());
我需要知道加密消息时CryptoJS使用的AES参数(不确定它们是什么,但听起来像:密钥大小(256),填充(pkcs5),模式(CBC),PBE算法(PBKDF2),盐(随机),迭代计数(100))。如果有人能证实这一点会有很大的帮助......过去几天我一直试图解开这个谜团。
我需要知道CryptoJS在AES加密消息时执行的不同步骤
答案 0 :(得分:9)
CryptoJS uses用于密钥派生的非标准化OpenSSL KDF(EvpKDF),其中 MD5 作为散列算法和 1 迭代。 IV也来自密码,这意味着在Java端只需要实际的密文,密码和盐来解密它。
换句话说,PBKDF2不用于CryptoJS的密码模式中的密钥派生。默认情况下,AES-256在CBC模式下使用PKCS5填充(same as PKCS7 padding)。请注意,您可能需要JCE Unlimited Strength Jurisdiction Policy Files。另请参阅Why there are limitations on using encryption with keys beyond certain length?
以下代码在Java中重新创建KDF(keySize
和ivSize
分别为8和4,用于AES-256并来自)。
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
答案 1 :(得分:2)
在@Artjom B在此问题和here for python users上都给出了很好的答案之后,我加入了完整的Java代码,该代码可帮助我解密以这种方式加密的字符串
var encrypted = CryptoJS.AES.encrypt(message, password).toString();
当您仅知道密码(即未与加密字符串一起发送salt)时,这段Java代码非常有用:
public String decrypt(String encrypted, String password) throws Exception {
int keySize = 8;
int ivSize = 4;
// Start by decoding the encrypted string (Base64)
// Here I used the Android implementation (other Java implementations might exist)
byte[] cipherText = Base64.decode(encrypted, Base64.DEFAULT);
// prefix (first 8 bytes) is not actually useful for decryption, but you should probably check that it is equal to the string "Salted__"
byte[] prefix = new byte[8];
System.arraycopy(cipherText, 0, prefix, 0, 8);
// Check here that prefix is equal to "Salted__"
// Extract salt (next 8 bytes)
byte[] salt = new byte[8];
System.arraycopy(cipherText, 8, salt, 0, 8);
// Extract the actual cipher text (the rest of the bytes)
byte[] trueCipherText = new byte[cipherText.length - 16];
System.arraycopy(cipherText, 16, trueCipherText, 0, cipherText.length - 16);
byte[] javaKey = new byte[keySize * 4];
byte[] javaIv = new byte[ivSize * 4];
evpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, javaKey, javaIv);
Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(javaIv);
aesCipherForEncryption.init(Cipher.DECRYPT_MODE, new SecretKeySpec(javaKey, "AES"), ivSpec);
byte[] byteMsg = aesCipherForEncryption.doFinal(trueCipherText);
return new String(byteMsg, "UTF-8");
}
public 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 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
}
答案 2 :(得分:1)
我在这里看at the documentation:
生成密钥的东西:
您可以手动设置参数,这可能比依赖默认设置更安全,例如一些伪代码基于文档中的示例:
var salt = CryptoJS.lib.WordArray.random(128/8);
var iv = CryptoJS.lib.WordArray.random(128);
var key256Bits10000Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 256/32, iterations: 10000 }); //I don't know this is dividing by 32
var encrypted = CryptoJS.AES.encrypt("Message", key, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv:iv });
您可能需要进行实验。我一步一步走。通过摆弄这些参数来获取基于密码的密钥进行匹配,然后获取要匹配的密文,然后找出解密。避免简化跳过IV或使用ECB等事情的冲动。