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

背景:我正在处理的应用程序应该脱机工作。 我有一个HTML5页面,用户键入的数据使用crypto-js库加密。 我希望将加密的消息发送到java webserver,然后在服务器端解密。

我正在做什么我能够使用Crypto-js加密消息

 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=="  

我想要做的是将加密文本“D0GBMGzxKXU757RKI8hDuQ ==”传递给java服务器端代码并​​解密加密的消息。

我尝试了很多选项来解密java服务器端的crypto-js加密消息。 请在服务器端找到我的代码,该代码应该对加密文本进行解密。

  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; }  

我从下面的代码中调用java方法解密

  // performs decryption public static void main(String[] args) throws Exception { String decryptedText = CrypterUtil.decrypt("user password","D0GBMGzxKXU757RKI8hDuQ=="); }  

但是当我运行java解密代码时,我得到以下exception

  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.  

我不确定我做错了什么?…使用crypto-js库加密消息的最佳方法是什么,以便在使用用户密钥密码的情况下将其删除。

您必须了解密码不是密钥。 密码通常经过一些散列函数,以产生一个字符串或字节数组,这是一个密钥。 它无法打印,因此表示为hex或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 }); 

在服务器上,您需要将hex密钥字符串转换为字节数组 。 您还需要在服务器上调整从AESAES/CBC/PKCS5Padding因为它是AES/CBC/PKCS5Padding中的默认方案 。 注意AES的PKCS5和PKCS7是相同的。

另请注意,您需要将IV从客户端传递到服务器并将其初始化为

 chiper.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes)); 

您当然可以使用PBKDF的Java实现从服务器上的密码和salt重新创建密钥,或者只保存已知密码和salt的密钥。 您可以使用PBKDF的迭代来为您的用户接受。

感谢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); } 

AES和相关算法可以以多种不同的方式使用,并且在混合语言时,弄清楚客户端使用什么模式并将它们与服务器的模式相匹配总是有点棘手。

Java代码的第一个问题是您不能将字符串的字节用作AES密钥。 互联网上有很多这样做的例子,但这是非常错误的。 就像@ artjom-B使用CryptoJS代码一样,您需要使用“基于密码的密钥派生函数”,并且它还需要在客户端和服务器上进行完全相同的参数化。

此外,客户端需要生成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发送到服务器
  • 然后在服务器端,使用salt,迭代计数和密码生成AES密钥
  • 然后base64解码并解密它