4

I have an encrypted AES-256 string from CryptoJS with a passphrase. I need to decrypt it in Java but can't figure out how to do it. It seems that you need IV, key and salt to decrypt, and as in CryptoJS mainpage, the encrypted data already contains all of them, and CryptoJS can somehow parse them out of encrypted input.

Anyone know how to do that? I've seen so much example about CryptoJS - Java encrypt/decrypt but most of them use hardcoded IV/key, or just send IV/key from cryptoJS side to Java side. All I have is a passphrase, just like what this site do!

kientux
  • 1,782
  • 1
  • 17
  • 32
  • How did you create the ciphertext in CryptoJS (best to show code) and are you able to change the JS implementation? – Artjom B. Mar 19 '15 at 17:30
  • @ArtjomB. No, I can't change JS implementation. I believe that ciphertext is created simply by this: [AES - CryptoJS](https://code.google.com/p/crypto-js/#AES) because I can use it to decrypt the string. – kientux Mar 19 '15 at 17:51
  • But *how* is it created? Is it for example: `CryptoJS.AES.encrypt("message", "passphrase").toString()`? – Artjom B. Mar 19 '15 at 17:52
  • You can see in that link, `var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");`. [This](http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js) is the source code. – kientux Mar 19 '15 at 18:04

1 Answers1

9

When a message is encrypted in this way:

CryptoJS.AES.encrypt("message", "passphrase")

a password-based approach is used. For that CryptoJS generates a new salt and uses this salt in conjunction with the passphrase to derive the key and IV (I've recreated a derivation function for this question).
After the ciphertext is produced a special OpenSSL formatter is used to encode the ciphertext including the used salt:

var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);

You need to decode the Base64 encoded string to get the byte array out if it. Then you can check if the header matches:

byte[] ctBytes = Base64.getDecoder().decode(ciphertext.getBytes("UTF-8"));
System.out.println("Is salted: " + new String(Arrays.copyOf(ctBytes, 8)).equals("Salted__"));

After that you can recover the salt and ciphertext from it:

byte[] saltBytes = Arrays.copyOfRange(ctBytes, 8, 16);
byte[] ciphertextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.length);

Derive the key+IV:

byte[] key = new byte[keySize/8];
byte[] iv = new byte[ivSize/8];
EvpKDF(password.getBytes("UTF-8"), keySize, ivSize, saltBytes, key, iv);

And decrypt the ciphertext:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
        new IvParameterSpec(iv));
byte[] recoveredPlaintextBytes = cipher.doFinal(ciphertextBytes);
String recoveredPlaintext = new String(recoveredPlaintextBytes);

Complete code:

public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException {
    String ciphertext = "U2FsdGVkX1+0m/gle/XQX1shjnpveUrl1fO3oOlurPMlTks6+oQlEPfOrucihzEz";
    String plaintext = "This is some example plaintext";
    String password = "This is a very strong password";
    int keySize = 256;
    int ivSize = 128;

    // var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);
    byte[] ctBytes = Base64.getDecoder().decode(ciphertext.getBytes("UTF-8"));
    System.out.println("Is salted: " + Arrays.equals(Arrays.copyOf(ctBytes, 8), new byte[]{0x53, 0x61, 0x6c, 0x74, 0x65, 0x64, 0x5f, 0x5f}));
    System.out.println("Is salted: " + new String(Arrays.copyOf(ctBytes, 8)).equals("Salted__"));

    byte[] saltBytes = Arrays.copyOfRange(ctBytes, 8, 16);
    System.out.println("Salt matches: " + Arrays.equals(saltBytes, hexStringToByteArray("b49bf8257bf5d05f")));

    byte[] ciphertextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.length);
    System.out.println("CT matches: " + Arrays.equals(ciphertextBytes, hexStringToByteArray("5b218e7a6f794ae5d5f3b7a0e96eacf3254e4b3afa842510f7ceaee722873133")));

    byte[] key = new byte[keySize/8];
    byte[] iv = new byte[ivSize/8];
    EvpKDF(password.getBytes("UTF-8"), keySize, ivSize, saltBytes, key, iv);

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
    byte[] recoveredPlaintextBytes = cipher.doFinal(ciphertextBytes);
    String recoveredPlaintext = new String(recoveredPlaintextBytes);

    System.out.println("Recovered Plaintext: " + recoveredPlaintext);
    System.out.println("Expected Plaintext: " + plaintext);
}

public static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
    return EvpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
}

public static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
    keySize = keySize / 32;
    ivSize = ivSize / 32;
    int targetKeySize = keySize + ivSize;
    byte[] derivedBytes = new byte[targetKeySize * 4];
    int numberOfDerivedWords = 0;
    byte[] block = null;
    MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
    while (numberOfDerivedWords < targetKeySize) {
        if (block != null) {
            hasher.update(block);
        }
        hasher.update(password);
        block = hasher.digest(salt);
        hasher.reset();

        // Iterations
        for (int i = 1; i < iterations; i++) {
            block = hasher.digest(block);
            hasher.reset();
        }

        System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
                Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));

        numberOfDerivedWords += block.length/4;
    }

    System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
    System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);

    return derivedBytes; // key + iv
}

/**
 * Copied from https://stackoverflow.com/a/140861
 * */
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;
}

JavaScript code:

var pw = "This is a very strong password";
var pt = "This is some example plaintext";
var encrypted = CryptoJS.AES.encrypt(pt, pw);
encrypted.toString(); // U2FsdGVkX1+0m/gle/XQX1shjnpveUrl1fO3oOlurPMlTks6+oQlEPfOrucihzEz
encrypted.salt.toString(); // b49bf8257bf5d05f
encrypted.ciphertext.toString(); // 5b218e7a6f794ae5d5f3b7a0e96eacf3254e4b3afa842510f7ceaee722873133
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • Sorry for late reply, and thank you so much! It works very well. With my poor knowledge about cryptography, I may never figure it out. – kientux Mar 20 '15 at 03:10
  • 1
    @kientux It helps to read the source code since this is open source. I also didn't know any of this until questions about this came up here on SO. – Artjom B. Mar 20 '15 at 07:28
  • Sorry for asking you again, but I having problem with encrypting in Java. I try to implement encrypt method of CryptoJS and I think the progress will be: Generate salt -> Use your `EvpKDF` method to derive the key and IV -> Encrypt with `Cipher` -> Return base64 as result. I tried with this piece of code: [AES256Cryptor](https://gist.github.com/kientux/bb48259c6f2133e628ad) but there's is something wrong and I can't decrypt successful, with `BadPaddingException`. Sorry for bother you again. – kientux Nov 13 '15 at 07:00
  • Don't mind, I figured it out! And thank you again! After read carefully this line: `After the ciphertext is produced a special OpenSSL formatter is used to encode the ciphertext including the used salt:`, I knew that I must create 8 bytes from string `"Salted__"`, then concat 8 bytes of `saltBytes`, then the `cipherBytes`! Exactly what you do to decrypt from base64. Thank you, I have learn so much today. – kientux Nov 13 '15 at 08:18
  • @ArtjomB., what does EvpKDF mean? – James Selvakumar Jan 18 '17 at 05:52
  • 1
    @JamesSelvakumar It's the name of the password-based **k**ey **d**erivation **f**unction used in CryptoJS which is derived from [OpenSSL's `EVP_BytesToKey`](https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html) – Artjom B. Jan 18 '17 at 06:13