0
votes

I am in a situation where a JSON is encrypted in PHP's openssl_encrypt and needs to be decrypted in JAVA.

$encrypted = "...ENCRYPTED DATA...";
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
var_dump(strlen($secret)); // prints : int(370)

$iv = substr($encrypted, 0, 16);
$data = substr($encrypted, 16);
$decrypted = openssl_decrypt($data, "aes-256-cbc", $secret, null, $iv);

This $decrypted has correct data which is now decrypted.

Now, the problem is when I try to do same things in Java it doesn't work :(

String path = "/path/to/secret/saved/in/text";
String payload = "...ENCRYPTED DATA...";
StringBuilder output = new StringBuilder();

String iv = payload.substring(0, 16);
byte[] secret = Base64.getDecoder().decode(Files.readAllBytes(Paths.get(path)));
String data = payload.substring(16);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(), 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // This line throws exception : 

cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));

Here it is:

Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 370 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.init(Cipher.java:1394)
at javax.crypto.Cipher.init(Cipher.java:1327)
at com.sample.App.main(App.java:70)

I have already visited similar question like

AES-256 CBC encrypt in php and decrypt in Java or vice-versa

openssl_encrypt 256 CBC raw_data in java

Unable to exchange data encrypted with AES-256 between Java and PHP

and list continues.... but no luck there

btw, this is how encryption is done in PHP

$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
$iv = bin2hex(openssl_random_pseudo_bytes(8));
$enc = openssl_encrypt($plainText, "aes-256-cbc", $secret, false, $iv);
return $iv.$enc;

and yes, I forgot to mention that my JRE is already at UnlimitedJCEPolicy and I can't change PHP code.

I am totally stuck at this point and can't move forward. Please help out.

EDIT#1

byte[] payload = ....;
byte[] iv = ....;
byte[] secret = ....; // Now 370 bits
byte[] data = Base64.getDecoder().decode(payload);

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(secret, 0, 32), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv, 0, cipher.getBlockSize());

cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] output = cipher.doFinal(data);

System.out.println(new String(output).trim());

Above snippet seems to be working with openssl_encrypt

EDIT#2

I am not sure if this is correct, but following is what I have done and encryption-decryption on both side are working fine.

Encrypt in PHP, Decrypt in JAVA use AES/CBC/NoPadding

Encrypt in JAVA, Decrypt in PHP use AES/CBC/PKCS5Padding

2
Use a key with the correct length, for "aes-256" that is a 32-byte key.zaph
Why are you trying to use a 370-byte AES key?President James K. Polk
^ Because key generation is not in my hand. It is done at PHP end and work perfectly there.Raja

2 Answers

1
votes

I won't provide a complete solution, but there are a few differences you should take care of

Encoding:

String iv = payload.substring(0, 16);
String data = payload.substring(16);

are you sure the IV and data are the same in Java and PHP (The IV is string?)? If the data are encrypted, they should be treated as a byte array, not string. Just REALLY make sure they are THE SAME (print hex/base64 in php and java)

For the IV you at the end call iv.getBytes(), but the locale encoding may/will corrupt your values. The String should be use only when it's really string (text). Don't use string for binaries.

Simply treat data and iv as byte[]

Key generation according to the openssl

AES key must have length of 256 bit for aes-256-cbc used. The thing is - openssl by default doesn't use the provided secret as a key (I believe it can, but I don't know how it is to be specified in PHP).

see OpenSSL EVP_BytesToKey issue in Java

and here is the EVP_BytesToKey implementation: https://olabini.com/blog/tag/evp_bytestokey/

you should generate a 256 bit key usging the EVP_BytesToKey function (it's a key derivation function used by openssl).

Edit:

Maarten (in the comments) is right. The key parameter is the key. Seems the PHP function is accepting parameter of any length which is misleading. According to some articles (e.g. http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-instead-of) the key is trucated or padded to necessary length (so seems 370 bit key is truncated to length of 256 bits).

0
votes

According to your example, I wrote fully working code for PHP and Java:
AesCipher class: https://gist.github.com/demisang/716250080d77a7f65e66f4e813e5a636

Notes:
-By default algo is AES-128-CBC.
-By default init vector is 16 bytes.
-Encoded result = base64(initVector + aes crypt).
-Encoded/Decoded results present as itself object, it gets more helpful and get possibility to check error, get error message and get init vector value after encode/decode operations.

PHP:

$secretKey = '26kozQaKwRuNJ24t';
$text = 'Some text'
$encrypted = AesCipher::encrypt($secretKey, $text);
$decrypted = AesCipher::decrypt($secretKey, $encrypted);

$encrypted->hasError(); // TRUE if operation failed, FALSE otherwise
$encrypted->getData(); // Encoded/Decoded result
$encrypted->getInitVector(); // Get used (random if encode) init vector
// $decrypted->* has identical methods

JAVA:

String secretKey = "26kozQaKwRuNJ24t";
String text = "Some text";

AesCipher encrypted = AesCipher.encrypt(secretKey, text);
AesCipher decrypted = AesCipher.decrypt(secretKey, encrypted);

encrypted.hasError(); // TRUE if operation failed, FALSE otherwise
encrypted.getData(); // Encoded/Decoded result
encrypted.getInitVector(); // Get used (random if encode) init vector
// decrypted.* has identical methods