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, ""))); }
… 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, ""))); }
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 (…).
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)
Advertisement
Answer
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);