AnupRai
AnupRai

Reputation: 75

Java code for mcrypt_encrypt PHP function with MCRYPT_RIJNDAEL_256 cipher

I have a PHP code for encrypting a string(token). I need to generate this token and call the legacy backend API. Backend API decrypts this and validates it before allowing access to the API. We are building the client application in Java. So this code needs to be implemented in Java.

$stringToEncode="****String which needs to be encrypted. This string length is multiple of 32********************";
$key="32ByteKey-asdcxzasdsadasdasdasda";
    echo base64_encode(
        mcrypt_encrypt(
                    MCRYPT_RIJNDAEL_256,
                    $key,
                    $stringToEncode,
                    MCRYPT_MODE_CBC,
                    $key
                )
            );

Here IV is same as key. I tried with below Java code using "org.bouncycastle.crypto"

private static void encrypt(String key, String data) throws InvalidCipherTextException {
            
        byte[] givenKey = key.getBytes(Charset.forName("ASCII"));        
        final int keysize = 256;
        byte[] keyData = new byte[keysize / Byte.SIZE];
        System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
        KeyParameter keyParameter = new KeyParameter(keyData);
        BlockCipher rijndael = new RijndaelEngine(256);
        ZeroBytePadding c = new ZeroBytePadding();
        PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);
        CipherParameters ivAndKey = new ParametersWithIV(keyParameter, key.getBytes(Charset.forName("ASCII")));
        pbbc.init(true, ivAndKey);
        byte[] plaintext = data.getBytes(Charset.forName("UTF8"));
        byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];
        int offset = 0;
        offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
        offset += pbbc.doFinal(ciphertext, offset);
        System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(ciphertext));
    }

But getting below exception -

Exception in thread "main" java.lang.IllegalArgumentException: invalid parameter passed to Rijndael init - org.bouncycastle.crypto.params.ParametersWithIV

I even tried with "javax.crypto" as shown below -

import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class aes{
    
     public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
            String key = "32ByteKey-asdcxzasdsadasdasdasda";
            String data = "****String which needs to be encrypted. This string length is multiple of 32********************";
            encrypt(key, data);
        }
    
    public static void encrypt(String key1, String data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
        
        byte[] key = key1.getBytes(Charset.forName("ASCII"));
        byte[] decrypted = data.getBytes(Charset.forName("UTF8"));      
        
        IvParameterSpec ivSpec = new IvParameterSpec(key);

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivSpec);
        byte[] encrypted = Base64.getEncoder().encodeToString(cipher.doFinal(decrypted)).getBytes();

        System.out.println(encrypted);
    }   
}

But getting beow exception -

Exception in thread "main" java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long

Can someone please suggest on how can I implement this?

Upvotes: 3

Views: 504

Answers (1)

Topaco
Topaco

Reputation: 49141

The PHP code uses Rijndael with a blocksize of 256 bits. This isn't supported by the SunJCE Provider, only AES (which is a subset of Rindael with a blocksize of 128 bits). For this reason the second Java code snippet doesn't work either. Therefore a third party provider like BouncyCastle (BC) must be applied.

There are two bugs in the Java / BC code:

  • Contrary to the PHP code no CBC is applied. This must be changed using CBCBlockCipher.
  • The Zero padding variant of BC is different from the mcrypt variant. The BC variant always pads, i. e. even if the length of the plaintext already corresponds to an integer multiple of the blocksize (32 bytes here), the mcrypt variant doesn't (i. e. the latter only pads if the length of the plaintext doesn't correspond to an integer multiple of the blocksize).
    Since according to the description the plaintext already corresponds to an integer multiple of the blocksize, the mcrypt variant doesn't pad. In the Java / BC code, this can be achieved if simply no padding is used at all.
    But be careful: If the length of the plaintext doesn't correspond to an integer multiple of the blocksize, padding must of course be used (with regard to compatibility with the Zero padding variant used in the PHP code).

Both problems can be solved by replacing the lines

ZeroBytePadding c = new ZeroBytePadding();
PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);

by

BufferedBlockCipher pbbc = new BufferedBlockCipher(new CBCBlockCipher(rijndael));

Then the ciphertexts produced by both codes are identical.

A few additional notes:

  • Using the key as IV is generally not useful. Since, for security reasons, an IV may only be used once for a given key, a new key must be used here for each encryption. See also here.
    Usually in practice a fresh, random IV is generated for each encryption.
  • It's generally more secure to use a standard, i.e. AES and not Rijndael with a block size of 256 bits, which isn't a standard.
  • Zero-padding isn't reliable (besides, there are several variants which, as you have experienced, can cause problems). More reliable is PKCS7 padding.

Upvotes: 4

Related Questions