13

I am making an application which needs Java based AES Encryption and JavaScript based decryption. I am using the following code for encryption as a basic form.

public class AESencrp {

  private static final String ALGO = "AES";
  private static final byte[] keyValue = 
      new byte[] { 'A', 'b', 'c', 'd', 'e', 'f', 'g',
      'h', 'i', 'j', 'k','l', 'm', 'n', 'o', 'p'};

  public static String encrypt(String Data) throws Exception {
    Key key = generateKey();
    Cipher c = Cipher.getInstance(ALGO);
    c.init(Cipher.ENCRYPT_MODE, key);
    byte[] encVal = c.doFinal(Data.getBytes());
    String encryptedValue = new BASE64Encoder().encode(encVal);
    return encryptedValue;
  }


  private static Key generateKey() throws Exception {
    Key key = new SecretKeySpec(keyValue, ALGO);
    return key;
  }
}

The JavaScript that I am trying to use to decrypt is

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js">   </script>

var decrypted = CryptoJS.AES.decrypt(encrypted,"Abcdefghijklmnop").toString(CryptoJS.enc.Utf8);

But the JavaScript decryption is not working. I am new to this, could someone tell me a way to solve without changing the Java code block ?

I tried Base-64 decoding my text like this:

var words  = CryptoJS.enc.Base64.parse(encrKey);
var base64 = CryptoJS.enc.Base64.stringify(words);
var decrypted = CryptoJS.AES.decrypt(base64, "Abcdefghijklmnop");
alert("dec :" +decrypted);

but still no good.

I tried the solution suggested below to resolve possible padding issue but its not giving any solution.

var key = CryptoJS.enc.Base64.parse("QWJjZGVmZ2hpamtsbW5vcA==");
var decrypt = CryptoJS.AES.decrypt( encrKey, key, { mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7 } );

alert("dec :" +decrypt);
rkj
  • 793
  • 1
  • 6
  • 10
  • "*But the JavaScript decryption is not working*" > Can you please elaborate on this? – Duncan Jones Mar 24 '14 at 11:58
  • I mean the page stops execution at the decryption line, I think there is some error in the way I am trying to decrypt it. – rkj Mar 24 '14 at 12:00
  • Is there an error printed in the JavaScript console? My first guess would be that you've not base64-decoded the ciphertext before you tried to decrypt it with CryptoJS. See https://code.google.com/p/crypto-js/#Encoders. – Duncan Jones Mar 24 '14 at 12:02
  • Your attempt to base64 looks broken. Should it be: `var words = CryptoJS.enc.Base64.parse(encrKey); var decrypted = CryptoJS.AES.decrypt(words, "Abcdefghijklmnop");`? – Duncan Jones Mar 24 '14 at 12:19
  • I tried doing as you said but still didnt work. I was wondering if the problem is that I am using secretkeyspec to generate key in java block and cryptojs in the js to decrypt, does it make any difference ? – rkj Mar 24 '14 at 12:25
  • I suspect it will be a difference in how the keys are treated. I think CryptoJS will derive a 256-bit AES key from your password, whereas in Java you are just using the raw bytes. – Duncan Jones Mar 24 '14 at 12:34
  • Can you suggest any way to resolve this. – rkj Mar 25 '14 at 07:00
  • @rkj Can you please provide the code for decrption in java, I have used your same code for javascript. – Arpit Agrawal Mar 28 '17 at 07:03

3 Answers3

19
  1. Your Java code uses the 128-bit AES key while your JavaScript code uses the 256-bit AES key.

  2. Your Java code uses the "Abcdefghijklmnop".getBytes() as the actual key value, while your JavaScript code uses the "Abcdefghijklmnop" as the passphrase from which the actual key is derived.

  3. The default transformation for Java AES is AES/ECB/PKCS5Padding, while default transformation for CryptoJS is AES/CBC/PKCS7Padding.

One way to fix your example is to fix the JavaScript side:

// this is Base64 representation of the Java counterpart
// byte[] keyValue = new byte[] { 'A', 'b', 'c', 'd', 'e', 'f', 'g',
//                'h', 'i', 'j', 'k','l', 'm', 'n', 'o', 'p'};
// String keyForJS = new BASE64Encoder().encode(keyValue);
var base64Key = "QWJjZGVmZ2hpamtsbW5vcA==";
console.log( "base64Key = " + base64Key );

// this is the actual key as a sequence of bytes
var key = CryptoJS.enc.Base64.parse(base64Key);
console.log( "key = " + key );

// this is the plain text
var plaintText = "Hello, World!";
console.log( "plaintText = " + plaintText );

// this is Base64-encoded encrypted data
var encryptedData = CryptoJS.AES.encrypt(plaintText, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
});
console.log( "encryptedData = " + encryptedData );

// this is the decrypted data as a sequence of bytes
var decryptedData = CryptoJS.AES.decrypt( encryptedData, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
} );
console.log( "decryptedData = " + decryptedData );

// this is the decrypted data as a string
var decryptedText = decryptedData.toString( CryptoJS.enc.Utf8 );
console.log( "decryptedText = " + decryptedText );
Oleg Estekhin
  • 8,063
  • 5
  • 49
  • 52
  • 1
    Nope, still not working. Are you sure that your "var decrypt" is correct because I think the key should be in UTF-8 format. Anyway I tried it but didnt work. – rkj Mar 25 '14 at 04:54
  • Your question now contains `CryptoJS.AES.decrypt( encrKey, key, ...)` near the end, and it seems that you are trying to decrypt the key with key, which meaningless. The first parameter should be the encrypted data in base64. – Oleg Estekhin Mar 25 '14 at 05:52
  • Thats just the naming convention that I was following. The first parameter is indeed the encrypted data. – rkj Mar 25 '14 at 05:58
  • 1
    Try http://jsfiddle.net/pKNzV/ (the output is in the console) and try to encode the same data in Java and in that example. And do not forget to use the same key (use new `BASE64Encoder().encode(keyValue)` in Java to get the `base64Key` value for JS) – Oleg Estekhin Mar 25 '14 at 07:05
  • Its working !! Actually I was implementing the javascript using cordova and phonegap and there was an issue regarding the scripts not being downloaded. Once I downloaded the original script in the source code it statred working. – rkj Mar 25 '14 at 11:09
  • @OlegEstekhin I tried doing what solution you have proposed but its not working for me. I have used the same java code and converted the Key to to Base64 for JS using the code String keyForJS = Base64.encodeBase64(keyValue).toString(); Now in you fiddle, if i give the generated key and generated encrypted value from Java, it does not decrypt the text. Can you please help – Achyut Oct 11 '14 at 14:35
  • @OlegEstekhin Can you try a fiddle which takes the input from a Java output and then decrypt it. I have a similar issue here - http://stackoverflow.com/questions/26315885/java-encryption-and-javascript-decryption?noredirect=1#comment41303566_26315885 – Achyut Oct 13 '14 at 10:33
6

For Java and JavaScript to able to inter operate, it is essential that no defaults are used while creating Key or the Cipher. The iteration count, key length, padding, salt and IV should all be the same.

Reference: https://github.com/mpetersen/aes-example

Sample code below:

Encrypting String in Java:

    String keyValue = "Abcdefghijklmnop";     
    SecretKeyFactory factory =   SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(keyValue.toCharArray(), hex("dc0da04af8fee58593442bf834b30739"),
        1000, 128);

    Key key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    Cipher c = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
    c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(hex("dc0da04af8fee58593442bf834b30739")));

    byte[] encVal = c.doFinal("The Quick Brown Fox Jumped over the moon".getBytes());
    String base64EncodedEncryptedData = new String(Base64.encodeBase64(encVal));
    System.out.println(base64EncodedEncryptedData);

}

Decrypting the same string in JavaScript:

var iterationCount = 1000;
var keySize = 128;
var encryptionKey  ="Abcdefghijklmnop";
var dataToDecrypt = "2DZqzpXzmCsKj4lfQY4d/exg9GAyyj0hVK97kPw5ZxMFs3jQiEQ6LLvUsBLdkA80" //The base64 encoded string output from Java;
var iv = "dc0da04af8fee58593442bf834b30739"
var salt = "dc0da04af8fee58593442bf834b30739"

var aesUtil = new AesUtil(keySize, iterationCount);
var plaintext =  aesUtil.decrypt(salt, iv, encryptionKey, dataToDecrypt);
console.log(plaintext);

**//AESUtil - Utility class for CryptoJS**
var AesUtil = function(keySize, iterationCount) {
 this.keySize = keySize / 32;
 this.iterationCount = iterationCount;
};

AesUtil.prototype.generateKey = function(salt, passPhrase) {
  var key = CryptoJS.PBKDF2(passPhrase, CryptoJS.enc.Hex.parse(salt),
  { keySize: this.keySize, iterations: this.iterationCount });
  return key;
}

AesUtil.prototype.decrypt = function(salt, iv, passPhrase, cipherText) {
  var key = this.generateKey(salt, passPhrase);
  var cipherParams = CryptoJS.lib.CipherParams.create({
    ciphertext: CryptoJS.enc.Base64.parse(cipherText)
  });
  var decrypted = CryptoJS.AES.decrypt(cipherParams,key,
  { iv: CryptoJS.enc.Hex.parse(iv) });
  return decrypted.toString(CryptoJS.enc.Utf8);
 }
}
Pulkit Gupta
  • 99
  • 1
  • 5
0

After spending 10 minutes in Java and 10 hours in JS I found working solution for AES/CBC/PKCS5Padding

Java/Kotlin Cypher side:

companion object {
    private const val ENCRYPTION_ALGORITHM = "AES"
    private const val BLOCK_OPERATION_MODE = "CBC"
    private const val PADDING_TYPE = "PKCS5Padding"
    private const val ENCRYPTION_MODE =
        "$ENCRYPTION_ALGORITHM/$BLOCK_OPERATION_MODE/$PADDING_TYPE"
    private const val SPLITTER = "\\."
    private const val AES_IV_SIZE = 16
    private const val DEFAULT_SALT_SIZE = 32
    private const val DEFAULT_ITERATIONS = 128
    private const val DEFAULT_AES_KEY_SIZE = 128
    private const val INDEX_SALT = 0
    private const val INDEX_IV = 1
    private const val INDEX_ENCRYPTED_DATA = 2
}

private fun encrypt(password: String, data: String): String {
    val random = SecureRandom()
    val iv = ByteArray(AES_IV_SIZE)
    random.nextBytes(iv)
    val salt = ByteArray(DEFAULT_SALT_SIZE)
    random.nextBytes(salt)
    val key = pbkdf2(password, salt)
    val cipher = Cipher.getInstance(ENCRYPTION_MODE)
    cipher.init(
        Cipher.ENCRYPT_MODE,
        SecretKeySpec(key, ENCRYPTION_ALGORITHM),
        IvParameterSpec(iv)
    )
    val encrypted = cipher.doFinal(data.toByteArray(StandardCharsets.UTF_8))
    return toBase64(salt) + "." + toBase64(iv) + "." + toBase64(encrypted)
}

/**
 * Generates PBKDF2 hash for the configured password using the provided salt.
 * If you notice very slow performance this can be related to Android Studio Instant Run
 *
 * @param salt The salt to use.
 * @return The password hash as byte array
 */
private fun pbkdf2(password: String, salt: ByteArray): ByteArray {
    val keySpec: KeySpec =
        PBEKeySpec(password.toCharArray(), salt, DEFAULT_ITERATIONS, DEFAULT_AES_KEY_SIZE)
    val keyFactory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
    val secretKey: SecretKey = keyFactory.generateSecret(keySpec)
    return secretKey.encoded
}

private fun toBase64(data: ByteArray): String {
        // use NO_WRAP because https://code.google.com/p/android/issues/detail?id=159799
        return Base64.encodeToString(data, Base64.NO_WRAP)
}

JS Crypto side:

function decrypt(password, encrypted) {
  const digest = encrypted.split('.');
  const salt = CryptoJS.enc.Base64.parse(digest[0])
  const iv = CryptoJS.enc.Base64.parse(digest[1])
  const encryptedData = CryptoJS.enc.Base64.parse(digest[2])
  const key = CryptoJS.PBKDF2(password, salt, {
    iterations: 128,
    keySize: 128/32,
  })
  const decrypted = CryptoJS.AES.decrypt(
    {
      ciphertext: encryptedData
    },
    key,
    {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    }
  ).toString(CryptoJS.enc.Utf8);
  return JSON.parse(decrypted);
}
Andoctorey
  • 728
  • 9
  • 11