Jeremiah Mason
Jeremiah Mason

Reputation: 33

javax.crypto.BadPaddingException: Given final block not properly padded - Strange error

javax.crypto.BadPaddingException: Given final block not properly padded - This is the error which I've got. I do not know why. My code seems to be alright. The key is the same during encrypting and decrypting. What I can say is that none of the already answered questions contain solution for my problem. Here is my code:

public String decrypt(String text) throws Exception {

    String key = "SiadajerSiadajer"; // 128 bit key
    // Create key and cipher
    Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
    Cipher cipher = Cipher.getInstance("AES");
    // encrypt the text
    byte[]encrypted = text.getBytes();
    cipher.init(Cipher.DECRYPT_MODE, aesKey);
    String decrypted = new String(cipher.doFinal(encrypted)); //Here is the error

    return decrypted;


}

And the encryption mechanism in php

function encrypt($data, $size)
{
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}

function decrypt($data)
{
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$key = "SiadajerSiadajer";
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
$name = openssl_encrypt(encrypt($name, 16), 'AES-256-CBC', $key, 0, $iv);

EDIT

Now in the php part my code is:

$key = "SiadajerSiadajer";
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
$EIV = base64_encode($iv);
$name = openssl_encrypt(encrypt($name, 16), 'AES-256-CBC', $key, 0, $EIV);

And it gives me warning:Warning: openssl_encrypt(): IV passed is 24 bytes long which is longer than the 16 expected by selected cipher, truncating in C:\xampp2\htdocs\standardfinalinserting.php on line 68

And in Java part my decryption method is exactly like in the answer for my question but after running it gives me an error: java.security.InvalidKeyException: Illegal key size on the line:

cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

EDIT 2:

So this is my whole Main class. It contains your whole Java code example

public class Main {

    private byte[] padKey(byte[] key) {
        byte[] paddedKey = new byte[32];
        System.arraycopy(key, 0, paddedKey, 0, key.length);
        return paddedKey;
    }

    private byte[] unpad(byte[] data) {     
        byte[] unpaddedData = new byte[data.length - data[data.length - 1]];
        System.arraycopy(data, 0, unpaddedData, 0, unpaddedData.length);
        return unpaddedData;
    }

    public String decrypt(String encodedJoinedData) throws Exception {

        // Base64-decode the joined data
        byte[] joinedData = Base64.decode(encodedJoinedData); 

        // Get IV and encrypted data
        byte[] iv = new byte[16];
        System.arraycopy(joinedData, 0, iv, 0, iv.length);
        byte[] encryptedData = new byte[joinedData.length - iv.length];
        System.arraycopy(joinedData, iv.length, encryptedData, 0, encryptedData.length);

        // Pad key
        byte[] key = padKey("SiadajerSiadajer".getBytes()); 
        Key aesKey = new SecretKeySpec(key, "AES");

        // Specify CBC-mode
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 
        cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec); //HERE IS THE ERROR

        // Decrypt data
        byte[] decryptedData = cipher.doFinal(encryptedData);

        // Remove custom padding
        byte[] unpaddedData = unpad(decryptedData);         

        return new String(unpaddedData);
    }

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
         String encodedJoinedData = "RB6Au33KGOF1/z3BQKqievUmh81Y8q4uw7s4vEs9xurQmNZAKwmhRQtS9wGYKQj8cJaPpK2xaDzx3RppQ8PsM/rQ9/p0Lme+x75dozBEbmFn6Q9eCXOCiCivVsKzZ8Vz";
            String decryptedData = new Main().decrypt(encodedJoinedData);
            System.out.println(decryptedData + " - " + decryptedData.length());



     }



}

Running the code results in error:

Exception in thread "main" java.security.InvalidKeyException: Illegal key size
    at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1039)
    at javax.crypto.Cipher.implInit(Cipher.java:805)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
    at javax.crypto.Cipher.init(Cipher.java:1396)
    at javax.crypto.Cipher.init(Cipher.java:1327)
    at com.dd.escuel.Main.decrypt(Main.java:43)
    at com.dd.escuel.Main.main(Main.java:57)

Upvotes: 0

Views: 1431

Answers (1)

Topaco
Topaco

Reputation: 49221

There are a few issues with the Java-code:

  1. In the PHP-code AES-256 is used, thus, the key must have a length of 32 Byte. Shorter keys are automatically right padded with zeros. This happens in your PHP-code since your key SiadajerSiadajer has a length of only 16 Byte. The key-padding must also be done in the Java-code. For this, e.g. the following Java-method can be used:

    private byte[] padKey(byte[] key) {
        byte[] paddedKey = new byte[32];
        System.arraycopy(key, 0, paddedKey, 0, key.length);
        return paddedKey;
    }
    
  2. With Cipher.getInstance("AES") the ECB-mode and PKCS5Padding are chosen by default. The CBC-mode must be explicitly specified in the Java-code with

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    
  3. In the PHP-method openssl_encrypt the fourth parameter $options is set to 0 which means that the returned data are Base64-encoded. Thus, in the Java-code the data have to be Base64-decoded before decryption:

    byte[]encryptedData = Base64.decode(text); 
    
  4. Since the CBC-mode is used the IV of the encryption must be considered. A possible approach is to Base64-encode the IV in the PHP-code (subsequent to the encryption) with

    $encodedIV = base64_encode($iv);
    

    and pass that value to the Java-method decrypt as second parameter. Here, the IV must be decoded and used for the decryption:

    byte[] iv = Base64.decode(ivEncoded);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 
    ...
    cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
    
  5. In the PHP-method openssl_encrypt the fourth parameter $options is set to 0 which means that the default padding (PKCS7) is used. Moreover, in the PHP-method encrypt a kind of custom padding is implemented (btw: the name of the method is unsuitable since it doesn't encrypt) and thus, it's padded twice. Therefore, after the decryption the custom padding (which may consist of white spaces) must be removed in the Java-code:

    byte[] unpaddedData = unpad(decryptedData);
    

    with

    private byte[] unpad(byte[] data) {     
        byte[] unpaddedData = new byte[data.length - data[data.length - 1]];
        System.arraycopy(data, 0, unpaddedData, 0, unpaddedData.length);
        return unpaddedData;
    }
    

Altogether:

public String decrypt(String text, String ivEncoded) throws Exception {

    // Pad key
    byte[] key = padKey("SiadajerSiadajer".getBytes()); 
    Key aesKey = new SecretKeySpec(key, "AES");

    // Base64 decode data
    byte[]encryptedData = Base64.decode(text); 

    // Base64 decode iv
    byte[] iv = Base64.decode(ivEncoded);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 

    // Specify CBC-mode
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    byte[] decryptedData = cipher.doFinal(encryptedData);

    // Remove custom padding
    byte[] unpaddedData = unpad(decryptedData);         

    return new String(unpaddedData);
}

Test:

PHP-code input: Plain text ($data), key ($key):

$data = 'This is a plain text which needs to be encrypted...';
$key = "SiadajerSiadajer";

PHP-code output: Base64 encoded IV ($encodedIV), encrypted data ($name):

$encodedIV = 'Dg+Zs3mqIJeDOOEPMT5F4Q==';
$name = '8dXjeQhx2WSswQOQXLcyMKNpa5s413yI2Ku8WiIB/xtA2pEjrKcl5kWtrOh9k4A12Jl0N/z6tH67Wybhp/OwTi1NtiJOZxl3w6YQufE29oU=';

If that output is used as input for the Java-method decrypt the decrypted data equals the plain text.

Concerning the PHP-code I would suggest to remove either the custom or the default (PKCS7) padding (if you have the choice). The latter can be achieved by using the flag OPENSSL_ZERO_PADDING as fourth parameter in the openssl_encrypt method (note: that flag doesn't mean "pad with zero-values", but "no padding"). If the custom padding is kept, you should at least rename the PHP-methods encrypt and decrypt to pad and unpad (or something similar), respectively.

As stated already in the comments, the GCM-mode may be a better choice than the-CBC mode. However, it would be useful to get informed about the basics before coding, e.g. difference between CBC- and GCM-mode, explanation of the GCM-mode here and here and the pitfalls coming along with the GCM-mode (GCM is secure, but only if you follow certain guidelines, e.g. a uniqe IV/nonce for each message encrypted with the same key).

You can use the PHP-method openssl_get_cipher_methods to find out the permissible AES-modes supported in PHP. This is explained in more detail here. For AES-256 and the AES-mode GCM you have to specify aes-256-gcm (with lowercase(!) letters). Presumably, that's why you get the "Unknown cipher algorithm"-error.

EDIT:

You can use for the encryption the following PHP-code (which is a slightly modified version of your PHP-code from the question):

<?php
function pad($data, $size) {
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}
function unpad($data) {
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$data = 'This is a plain text which needs to be encrypted...';
$key = "SiadajerSiadajer";
$iv_size = 16; 
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
$encryptedData = openssl_encrypt(pad($data, 16), 'AES-256-CBC', $key,  0, $iv);
print base64_encode($iv)."\n".$encryptedData."\n";

EDIT 2:

The IV and the encrypted data can be joined before or after the Base64-encoding. The first is more efficient and thus, I've implemented this variant. However, some changes are needed in the PHP- and Java-code and therefore, I post all methods which have changed.

The PHP-code becomes:

<?php
function pad($data, $size) {
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}
function unpad($data) {
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$data = 'This is a plain text which needs to be encrypted...';
$key = "SiadajerSiadajer";
$iv_size = 16; 
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
$encryptedData = openssl_encrypt(pad($data, 16), 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$joinedData = $iv.$encryptedData;
$encodedJoinedData = base64_encode($joinedData);
print $encodedJoinedData."\n";

And the Jave-method decrypt becomes:

public String decrypt(String encodedJoinedData) throws Exception {

    // Base64-decode the joined data
    byte[] joinedData = Base64.decode(encodedJoinedData); 

    // Get IV and encrypted data
    byte[] iv = new byte[16];
    System.arraycopy(joinedData, 0, iv, 0, iv.length);
    byte[] encryptedData = new byte[joinedData.length - iv.length];
    System.arraycopy(joinedData, iv.length, encryptedData, 0, encryptedData.length);

    // Pad key
    byte[] key = padKey("SiadajerSiadajer".getBytes()); 
    Key aesKey = new SecretKeySpec(key, "AES");

    // Specify CBC-mode
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 
    cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt data
    byte[] decryptedData = cipher.doFinal(encryptedData);

    // Remove custom padding
    byte[] unpaddedData = unpad(decryptedData);         

    return new String(unpaddedData);
}

and an example for the Java-method main is:

public static void main(String[] args) throws Exception {
    String encodedJoinedData = "RB6Au33KGOF1/z3BQKqievUmh81Y8q4uw7s4vEs9xurQmNZAKwmhRQtS9wGYKQj8cJaPpK2xaDzx3RppQ8PsM/rQ9/p0Lme+x75dozBEbmFn6Q9eCXOCiCivVsKzZ8Vz";
    String decryptedData = new Main().decrypt(encodedJoinedData);
    System.out.println(decryptedData + " - " + decryptedData.length());
}

The usage is as follows:

  1. Encrypt your plain text with the PHP-code. In the example above (PHP-code) the plain text is

    $data = 'This is a plain text which needs to be encrypted...';
    
  2. Then, pass the string contained in $encodedJoinedData to the Java-method decrypt. In the example above (main-method) the string is

    String encodedJoinedData = "RB6Au33KGOF1/z3BQKqievUmh81Y8q4uw7s4vEs9xurQmNZAKwmhRQtS9wGYKQj8cJaPpK2xaDzx3RppQ8PsM/rQ9/p0Lme+x75dozBEbmFn6Q9eCXOCiCivVsKzZ8Vz";
    
  3. The Java-method decrypt will provide the initial plain text.

A final note: If you decide to remove the (redundant) custom padding replace in the PHP-code the line

$encryptedData = openssl_encrypt(pad($data, 16), 'AES-256-CBC', $key,  OPENSSL_RAW_DATA, $iv);

with

$encryptedData = openssl_encrypt($data, 'AES-256-CBC', $key,  OPENSSL_RAW_DATA, $iv);

and remove the pad- and the unpad-method.

In the Java-code replace the lines

// Remove custom padding
byte[] unpaddedData = unpad(decryptedData);         

return new String(unpaddedData);

with

return new String(decryptedData);

and remove the unpad-method.

Edit 3:

The InvalidKeyException (Illegal key size) mentioned in the Edit2-section of the question is already discussed e.g. here InvalidKeyException Illegal key size and here Java Security: Illegal key size or default parameters?.

Upvotes: 2

Related Questions