背景 我正在处理的应用程序应该脱机工作。我有一个HTML5页面,用户键入的数据使用crypto-js库加密。 我希望将加密的消息发送到java webserver,然后在服务器端解密。
正在做什么 我能够使用Crypto-js加密消息
<code>
var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());
// this prints an encrypted text "D0GBMGzxKXU757RKI8hDuQ=="
</code>
我想要做的是传递加密文本&#34; D0GBMGzxKXU757RKI8hDuQ == &#34;到java服务器端代码并解密加密的消息。
我尝试了很多选项来解密java服务器端的crypto-js加密消息。 请在服务器端找到我的代码,该代码应该对加密文本进行解密。
<code>
public static String decrypt(String keyText,String encryptedText)
{
// generate key
Key key = new SecretKeySpec(keyText.getBytes(), "AES");
Cipher chiper = Cipher.getInstance("AES");
chiper.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
byte[] decValue = chiper.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
</code>
我从下面的代码
中调用java方法解密<code>
// performs decryption
public static void main(String[] args) throws Exception
{
String decryptedText = CrypterUtil.decrypt("user password","D0GBMGzxKXU757RKI8hDuQ==");
}
</code>
但是当我运行java解密代码时,我得到以下异常
<code>
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(AESCipher.java:372)
at javax.crypto.Cipher.passCryptoPermCheck(Cipher.java:1052)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1010)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at au.gov.daff.pems.model.utils.CrypterUtil.decrypt(CrypterUtil.java:34)
at au.gov.daff.pems.model.utils.CrypterUtil.main(CrypterUtil.java:47)
Process exited with exit code 1.
</code>
我不确定我做错了什么?...使用crypto-js库加密邮件的最佳方法是什么,以便在使用用户密码密码的情况下将其删除。
答案 0 :(得分:3)
您必须了解密码不是密钥。密码通常经过一些散列函数,以产生一个字符串或字节数组,这是一个密钥。它无法打印,因此它表示为十六进制或base64。
在JavaScript中,您使用密码,但在Java中,您假设相同的密码是它不是的密钥。您可以确定CryptoJS如何使用密码来获取密钥并在Java中重新创建密钥,但似乎它的实现方式是每次使用密码加密时都会生成新的盐,并且无法改变盐。
如果您真的想工作将来自用户的密码,那么您需要自己导出密钥。 CryptoJS为此提供了PBKDF2,但它也需要盐。您可以为您的应用程序生成一个并将其添加到代码中。你会这样生成一次:
CryptoJS.lib.WordArray.random(128/8).toString();
每次将静态盐传递给基于密码的密钥派生函数(此处为AES-256)时,派生密钥
var key = CryptoJS.PBKDF2(userPassword,
CryptoJS.enc.Hex.parse(salt),
{ keySize: 256/32, iterations: 1000 });
var iv = CryptoJS.lib.WordArray.random(256/8); // random IV
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
在服务器上,您需要convert the hex key string into a byte array。您还需要在服务器上将该方案从AES
调整为AES/CBC/PKCS5Padding
,因为它是default in CryptoJS。注意AES的PKCS5和PKCS7是相同的。
另请注意,您需要将IV从客户端传递到服务器并将其初始化为
chiper.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
您当然可以使用PBKDF的Java实现从服务器上的密码和salt重新创建密钥,或者只保存已知密码和salt的密钥。您可以使用PBKDF的迭代来解决用户可接受的问题。
答案 1 :(得分:3)
感谢Artjom B和Isaac Potoczny-Jones的迅速回应和建议。我正在提供对我有用的完整解决方案,以造福他人。
在Java服务器端解密cryptojs加密消息的Java代码
public static void main(String args[]) throws Exception{
String password = "Secret Passphrase";
String salt = "222f51f42e744981cf7ce4240eeffc3a";
String iv = "2b69947b95f3a4bb422d1475b7dc90ea";
String encrypted = "CQVXTPM2ecOuZk+9Oy7OyGJ1M6d9rW2D/00Bzn9lkkehNra65nRZUkiCgA3qlpzL";
byte[] saltBytes = hexStringToByteArray(salt);
byte[] ivBytes = hexStringToByteArray(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
SecretKeySpec sKey = (SecretKeySpec) generateKeyFromPassword(password, saltBytes);
System.out.println( decrypt( encrypted , sKey ,ivParameterSpec));
}
public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return new SecretKeySpec(secretKey.getEncoded(), "AES");
}
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;
}
public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, sKey, ivParameterSpec);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
可以在客户端进行加密和解密的cryptojs javascript代码
function generateKey(){
var salt = CryptoJS.lib.WordArray.random(128/8);
var iv = CryptoJS.lib.WordArray.random(128/8);
console.log('salt '+ salt );
console.log('iv '+ iv );
var key128Bits100Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key128Bits100Iterations '+ key128Bits100Iterations);
var encrypted = CryptoJS.AES.encrypt("Message", key128Bits100Iterations, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
}
function decrypt(){
var salt = CryptoJS.enc.Hex.parse("4acfedc7dc72a9003a0dd721d7642bde");
var iv = CryptoJS.enc.Hex.parse("69135769514102d0eded589ff874cacd");
var encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
var key = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key '+ key);
var decrypt = CryptoJS.AES.decrypt(encrypted, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
var ddd = decrypt.toString(CryptoJS.enc.Utf8);
console.log('ddd '+ddd);
}
答案 2 :(得分:2)
AES和相关算法可以以多种不同的方式使用,在混合语言时,弄清楚客户端使用的模式并将它们与服务器的模式相匹配总是有点棘手。
Java代码的第一个问题是您不能将字符串的字节用作AES密钥。互联网上有很多很多的例子,但这是非常错误的。就像@ artjom-B使用CryptoJS代码一样,您需要使用基于密码的密钥派生函数&#34;并且它需要也在客户端上完全相同地进行参数化。服务器
此外,客户端需要生成salt并将其与加密文本一起发送;否则,服务器无法从给定的密码生成相同的密钥。我不确定CryptoJS究竟是如何做到的,这在Java中是合理的,你可以在了解cryptoJS的工作原理时调整参数:
public static SecretKey generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}
使用AES CBC,您还需要随机生成IV并将其与加密文本一起发送。
总结如下: