如何使用crypto-js库在客户端加密消息并在Java服务器上解密它

时间:2014-11-27 22:55:18

标签: java security encryption cryptography cryptojs

背景 我正在处理的应用程序应该脱机工作。我有一个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库加密邮件的最佳方法是什么,以便在使用用户密码密码的情况下将其删除。

3 个答案:

答案 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并将其与加密文本一起发送。

总结如下:

  • 找出CryptoJS使用的AES参数。不知道它们是什么,但听起来像:密钥大小(256),填充(pkcs5),模式(CBC),PBE算法(PBKDF2),盐(随机),迭代计数(100)
  • 使用相同的参数配置服务器
  • 使用PBE密钥生成器以及非秘密(但随机)盐
  • 使用AES CBC和非秘密(但随机)IV
  • 将密文,IV和盐发送到服务器
  • 然后在服务器端,使用salt,迭代计数和密码生成AES密钥
  • 然后base64解码并解密