Search code examples
javascriptjavaspring-bootencryptioncryptojs

CryptoJs decrypt function equivalent in Java


I'm trying to decrypt a token that the server I'm using brings in order to load correctly users access to the page.

I already achieved the goal using CryptoJs using JavaScript. However now I need to transfer that function to my java backend.

Look my code using CryptoJs which works correctly

const token = "U2FsdGVkX1+6YueVRKp6h0dZfk/a8AC9vyFfAjxD4nb7mXsKrM7rI7xZ0OgrF1sShHYNLMJglz4+67n/I7P+fg=="
const key = "p80a0811-47db-2c39-bcdd-4t3g5h2d5d1a"

// Decrypt
const iv = CryptoJS.enc.Utf8.parse(key);
const decryptedToken = CryptoJS.AES.decrypt(token, key, {
  keySize: 16,
  iv,
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7,
});

const stringToken = decryptedToken.toString(CryptoJS.enc.Utf8);
const dataToUse = JSON.parse(stringToken)

console.log("decryptedToken => ", stringToken);
console.log("data => ",dataToUse);

And this is my code in Java using javax.crypto

public String decrypt() {
  String finalToken = null;
  Cipher cipher;
  try {
    String token = "U2FsdGVkX1+6YueVRKp6h0dZfk/a8AC9vyFfAjxD4nb7mXsKrM7rI7xZ0OgrF1sShHYNLMJglz4+67n/I7P+fg==";
    String key = "p80a0811-47db-2c39-bcdd-4t3g5h2d5d1a";
    Key skeySpec = new SecretKeySpec(Arrays.copyOf(key.getBytes(), 16), "AES");
    byte[] iv = Arrays.copyOf(key.getBytes(StandardCharsets.UTF_8), 16);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    byte[] bytesToDecrypt = Base64.decodeBase64(token.getBytes());
    cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
    byte[] decrypted = cipher.doFinal(bytesToDecrypt);
    finalToken = new String(decrypted);
    log.info("Decrypt token succesfully {}", finalToken);
  } catch (NoSuchAlgorithmException
      | InvalidAlgorithmParameterException
      | IllegalBlockSizeException
      | NoSuchPaddingException
      | BadPaddingException
      | InvalidKeyException e) {
    e.printStackTrace();
    log.error("Error decrypting");
  }

  return finalToken;
}

I'm not sure I'm setting correctly the key and the iv. With the piece of code above I get this error:

javax.crypto.BadPaddingException: pad block corrupted

If I don't "cut" the key and the iv in order to have 16 bytes I get wrong length error.

Can someone help me to figure out what's wrong please!

The expected result is to get this info in java so then I can manipulate the object:

{name: "Burak", surName: "Bayraktaroglu"}

Solution

  • Decryption fails because CryptoJS interprets the key material as password when passed as string (as in the posted code).
    In this case, the password and a salt are used to derive the key and IV applying the OpenSSL proprietary key derivation function EVP_BytesToKey().
    The random 8 bytes salt is generated during encryption, which returns as result the Base64 encoding of the concatenation of a prefix, the salt and the actual ciphertext. The prefix consists of the ASCII encoding of Salted__.

    Therefore, the following steps are necessary in the decryption process:

    • Determining salt and ciphertext from the CryptoJS return value
    • Deriving key and IV using password and salt
    • Decryption of the ciphertext with the derived key and IV

    There are several ways to implement the last two steps in Java.
    You can find various ports of EVP_BytesToKey() on the web which can be used in conjunction with the native JCA/JCE for decryption. These EVP_BytesToKey() ports are not always reliable, a solid port is this one.
    A convenient (and reliable) alternative is BouncyCastle (with its own EVP_BytesToKey() port), described below:

    import java.nio.ByteBuffer;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    import org.bouncycastle.crypto.digests.MD5Digest;
    import org.bouncycastle.crypto.engines.AESEngine;
    import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
    import org.bouncycastle.crypto.modes.CBCBlockCipher;
    import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
    import org.bouncycastle.crypto.params.ParametersWithIV;
    
    ...
    
    // Get salt and ciphertext
    String saltCiphertextB64 = "U2FsdGVkX1+6YueVRKp6h0dZfk/a8AC9vyFfAjxD4nb7mXsKrM7rI7xZ0OgrF1sShHYNLMJglz4+67n/I7P+fg==";
    byte[] saltCiphertext = Base64.getDecoder().decode(saltCiphertextB64);
    ByteBuffer byteBuffer = ByteBuffer.wrap(saltCiphertext);
    byte[] prefix = new byte[8];
    byte[] salt = new byte[8];
    byteBuffer.get(prefix);
    byteBuffer.get(salt);
    byte[] ciphertext = new byte[byteBuffer.remaining()];
    byteBuffer.get(ciphertext);
    
    // Key derivation via EVP_BytesToKey() (using BouncyCastle)
    String passwordStr = "p80a0811-47db-2c39-bcdd-4t3g5h2d5d1a";
    byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
    OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); 
    pbeGenerator.init(password, salt);
    ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits
    // FYI: key: ((KeyParameter)parameters.getParameters()).getKey()
    //      IV : parameters.getIV()
    
    // Decryption (using BouncyCastle)
    PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
    cipher.init(false, parameters);
    byte[] plaintext = new byte[cipher.getOutputSize(ciphertext.length)];
    int length = cipher.processBytes(ciphertext, 0, ciphertext.length, plaintext, 0);
    length += cipher.doFinal(plaintext, length);        
    String plaintextSr = new String(plaintext, 0, length, StandardCharsets.UTF_8);
    System.out.println(plaintextSr); // {"name":"Burak","surName":"Bayraktaroglu"}