4

I need to migrate data from a PHP to a JAVA application.

The data has been encrypted using this PHP function:

function encrypt($text, $encryptionKey) {
  return utf8_encode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, $text, MCRYPT_MODE_CBC, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")));
}

... and it can be decrypted successfully using this function:

function decrypt($text, $encryptionKey) {
  return utf8_decode(rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $encryptionKey, base64_decode($text), MCRYPT_MODE_CBC, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")));
}

It is stored in MySql-InnoDB with collation=latin1_swedish_ci in a table with charset=utf8 in a VARCHAR(255) column.


In JAVA the data is retrieved through a JPA-repository using Hibernate and then presented to this method based on Rijndael 256 encryption with Java & Bouncy Castle :

protected String decryptRijndael256_(String encryptedBase64Text) {
  byte[] encryptedBytes = Base64.getDecoder().decode(encryptedBase64Text.getBytes(StandardCharsets.UTF_8));
  byte[] key = encryptionKey.getBytes(StandardCharsets.UTF_8);

  RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
  KeyParameter keyParam = new KeyParameter(key);
  rijndaelEngine.init(false, keyParam);
  PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding());

  byte[] decryptedBytes = new byte[bufferedBlock.getOutputSize(encryptedBytes.length)];
  int processed = bufferedBlock.processBytes(encryptedBytes, 0, encryptedBytes.length, decryptedBytes, 0);
  processed += bufferedBlock.doFinal(decryptedBytes, processed);
  decryptedBytes = Arrays.copyOfRange(decryptedBytes, 0, processed);

  return new String(decryptedBytes, StandardCharsets.UTF_8);
}

While this works in most cases where the encrypted text looks like this:

5VTv/x2f41Aj2iES7B9lRUi8Q9gH3MYnSR3xc4X1di4= => account.name@gmail.com

... it fails if the text is long and looks like this:

p77KGdWlexQXLGPZzkAqk2OK6oC9r7TDfMfaDhofu0et7RaPcA0hUCq0mBnY4oakjZpIrBeMadwhYonVKwJlGw== => very.long.account.name@gmail.c���ե{.c��@*�c�ꀽ���|���G


So it is mostly decoded correctly, but the last block seems to be the problem. I suspect the problem lies either with character encoding or with the MCRYPT_MODE_CBC and its IV (\0\0...).

I have not found any way to add the mode and IV to the java implementation.

As for the Base64 encryption. I tried about any possible method from java.util.Base64, org.apache.commons.codec.binary.Base64 and org.bouncycastle.util.encoders.Base64.

The result is either as above or:

org.bouncycastle.crypto.DataLengthException: last block incomplete in decryption
    at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)

My guess is that the problem has to do with how PHP does the padding.


Question:

What is so different between how PHP and JAVA handle this decryption? How can I decode all values correctly?


(PHP: 5.6.40, JAVA: 11.0.7_10 Amazon Corretto)

Holly
  • 1,305
  • 2
  • 15
  • 30
  • FYI: The MCrypt extension has been deprecated for a _long_ time and was removed as of PHP7. Additionally, the Rijndael cipher is almost AES sometimes, but not really and you'll make your life easier if you migrate your data to actual AES. – Sammitch May 04 '20 at 18:37
  • @Sammitch well its legacy code, hence we are migrating - in a language I'm kinda allergic to - however it now looks like I may yet have to use some PHP for the migration, unless I get an answer here – Holly May 04 '20 at 19:40

1 Answers1

2

The problem is caused by using the ECB mode instead of the CBC mode in the posted code. To apply the CBC mode replace:

RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
KeyParameter keyParam = new KeyParameter(key);
rijndaelEngine.init(false, keyParam);
PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding());

with:

byte[] iv = new byte[32]; // 0-IV, analogous to PHP code
PaddedBufferedBlockCipher bufferedBlock = new PaddedBufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine(256)), new ZeroBytePadding());
CipherParameters keyAndIV = new ParametersWithIV(new KeyParameter(key), iv);
bufferedBlock.init(false, keyAndIV);

with key from the posted code. iv is a byte[] containing the IV, whose size corresponds to the blocksize (32 bytes). Analogous to the PHP code, a 0-IV has to be used (to decrypt the data encrypted with the PHP code). Note, however, that for security reasons a key/IV pair may only be used once. Therefore, if the key is fixed, it's best to use an IV, which is randomly generated for each encryption.

Furthermore, the code:

decryptedBytes = Arrays.copyOfRange(decryptedBytes, 0, processed);
return new String(decryptedBytes, StandardCharsets.UTF_8);

can be simplified to:

return new String(decryptedBytes, 0, processed, StandardCharsets.UTF_8);
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • you are a life saver, I had been looking at the implementations of `BlockCipherPadding` but I somehow missed looking at the other interface `BlockCipher` and its implementations – Holly May 05 '20 at 09:47
  • I seem to not have been clear about this, I need to decode the data so I can encrypt it in a better way in the new application, not just to read it as is, but your notes are well appreciated - the IV is kinda like the salt one should use when hashing I understand, so should be treated in a similar way – Holly May 05 '20 at 09:51
  • 1
    @Holly - With regard to a new implementation: In addition to the rules to be followed for the [IV](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_(IV)), also algorithm, mode and padding should be thoroughly reconsidered, e.g. AES-[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) and [Pkcs7](https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7)-Padding are more reliable alternatives. – Topaco May 05 '20 at 14:02
  • Clarification: AES-GCM _and_ Pkcs7-Padding is somehow misleading, since GCM doesn't use a padding. What I actually meant was: For a mode that _requires_ a padding (e.g. like CBC), Pkcs7 is more reliable than Zero padding. – Topaco May 05 '20 at 18:16