How to decrypt a cryptojs AES encrypted message at the java server side?

12,417

Solution 1

This part of your code is wrong:

KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128/32);
//->---------------------------------------------------------------------^^^^^^^

The 128/32 value is erroneous. You need either 128, 192 or 256. Currently you have the equivalent of 4, which seems to result in no output at all from the PBKDF2 function.

Also, in Java you should use DatatypeConverter.parseHexBinary(), or similar, to convert hex into bytes. Currently you are just calling getBytes() which isn't right.

Finally, you need to specify CBC mode and PKCS#5 padding in order to match your Javascript code. So change the line to:

Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");

Solution 2

Thanks to Duncan for the prompt response and advice. I am giving the complete solution that worked for me below for the benefit of others.

Java code to do the decryption of the cryptojs encrypted message

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;
}
Share:
12,417
user1455719
Author by

user1455719

Updated on June 05, 2022

Comments

  • user1455719
    user1455719 almost 2 years

    I have the following cryptojs based javascript encryption/decryption functions which works perfectly fine.

    I use a random salt, random iv value and a specific password while encrypting the message using cryptpjs. I reuse the same salt, iv and the password to generate the key while decrypting the encrypted message.

    This part works well..

    function  encrypt(){
      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 key128Bits = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32 }); 
      console.log( 'key128Bits '+ key128Bits);
      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  });
      console.log('encrypted   '+ encrypted  );
    }
    
    function  decrypt(){
      var salt = CryptoJS.enc.Hex.parse("4acfedc7dc72a9003a0dd721d7642bde");
      var iv = CryptoJS.enc.Hex.parse("69135769514102d0eded589ff874cacd");
      var encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
      console.log('salt  '+ salt );
      console.log('iv  '+ iv );
      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);
    } 
    

    But the issue starts when I try to decrypt the same encrypted text at the java server side. I want the encrypted message to be decrypted by my java server code. Here is the Java code that I wrote:

    public static void main(String args[]) throws Exception{
      String password = "Secret Passphrase";
      String salt = "4acfedc7dc72a9003a0dd721d7642bde";
      String iv = "69135769514102d0eded589ff874cacd";
      String encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
      byte[] saltBytes = salt.getBytes(); //hexStringToByteArray(salt);
      byte[] ivBytes = iv.getBytes();//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/32);
      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
      SecretKey secretKey = keyFactory.generateSecret(keySpec);
      return new SecretKeySpec(secretKey.getEncoded(), "AES");
    }
    
    public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception {
    
      Cipher c = Cipher.getInstance("AES");
      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;
    }
    

    But I get the following exception:

    Exception breakpoint: SecretKeySpec.java:96, java.lang.IllegalArgumentException, Empty key
    Exception in thread "main" java.lang.IllegalArgumentException: Empty key
    at javax.crypto.spec.SecretKeySpec.<init>(SecretKeySpec.java:96)
    

    I have no idea what I should do

  • user1455719
    user1455719 over 9 years
    The moment i did the changes you suggested , it worked . Thanks Duncan. I will provide the complete working solution as another answer. Thanks heaps. You were spot on and fixed my issues.
  • The Coder
    The Coder over 9 years
    I've used your Javascript code (in question) and Java code (in this answer) as example and I've implemented it. Java code is successfully working when the password, salt, iv and encrypted are hardcoded like done in this answer. I also used your javascript code for client side, but it seems like there is some misconfigurations in the js side like key size and so on, coz of that I'm getting bad padding exception in java when decoding the javascript encoded data. Can you provide the working javascript code..
  • The Coder
    The Coder over 9 years
    It's "Given Final block not properly padded" error in java
  • Joshua Ooi
    Joshua Ooi almost 3 years
    May i ask how can I implement this into Android Studio java? Because i got the error message unable to resolve "Base64Decoder" and i found that android studio doesnt support sun.misc package.