Search code examples
javaencryptionaespadding

Java AES encryption - How to fix IllegalBlockSizeException


I have two Java methods, one for encryption an another one for decryption. I would like to encrypt/decrypt a password with a master password. But when I try to decrypt the encrypted password i get Caused by: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher. I can't figure out, why I get it. I could imagine that it has something to do with the string/byte conversion, but I'm not sure. How can I fix it?

Code:

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

public class AESEncryption implements EncryptionModuleInterface {
    private static final int ITERATION_COUNT = 1000000;
    private static final int KEY_LENGTH = 256;
    private static final String PBKDF_ALGORITHM = "PBKDF2WithHmacSHA1";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";

    @Override
    public String encryptPassword(String password, String masterpassword) {
        byte[] finalCiphertext;

        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);

        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALGORITHM);
            byte[] key = factory.generateSecret(spec).getEncoded();
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);

            byte[] ivBytes = new byte[16];
            random.nextBytes(ivBytes);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

            byte[] inputBytes = password.getBytes();

            byte[] encValue = cipher.doFinal(inputBytes);
            finalCiphertext = new byte[encValue.length+2*16];
            System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
            System.arraycopy(salt, 0, finalCiphertext, 16, 16);
            System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);

        } catch (NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException |
                 InvalidKeySpecException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException(e);
        }

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

    @Override
    public String decryptPassword(String password, String masterpassword) {
        byte[] ivBytes = new byte[16];
        byte[] salt = new byte[16];
        byte[] encValue;

        byte[] readEncryptedBytesWithIvAndSaltPrefix = password.getBytes();
        byte[] inputBytes = new byte[readEncryptedBytesWithIvAndSaltPrefix.length - 32];

        System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 0, ivBytes, 0, 16);
        System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 16, salt, 0, 16);
        System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 32, inputBytes, 0, readEncryptedBytesWithIvAndSaltPrefix.length - 32);

        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALGORITHM);
            byte[] key = factory.generateSecret(spec).getEncoded();
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);

            IvParameterSpec iv = new IvParameterSpec(ivBytes);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);

            encValue = cipher.doFinal(inputBytes);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException | InvalidKeySpecException | InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }

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

Edit: I can convert the binary byte array to a hex string:

StringBuilder result = new StringBuilder();
        for (byte b : finalCiphertext){
            result.append(String.format("%02X", b));
        }

        return result.toString();

but I have problems to convert the hex string back to a binary byte array.

StringBuilder stringBuilder = new StringBuilder();
        HashMap<Character, String> hashMap = new HashMap<Character, String>();
        hashMap.put('0', "0000");
        hashMap.put('1', "0001");
        hashMap.put('2', "0010");
        hashMap.put('3', "0011");
        hashMap.put('4', "0100");
        hashMap.put('5', "0101");
        hashMap.put('6', "0110");
        hashMap.put('7', "0111");
        hashMap.put('8', "1000");
        hashMap.put('9', "1001");
        hashMap.put('A', "1010");
        hashMap.put('B', "1011");
        hashMap.put('C', "1100");
        hashMap.put('D', "1101");
        hashMap.put('E', "1110");
        hashMap.put('F', "1111");

        for (int i = 0; i < password.length(); i++) {
            stringBuilder.append(hashMap.get(password.charAt(i)));
        }

        String binaryString = stringBuilder.toString();
        byte[] readEncryptedBytesWithIvAndSaltPrefix = binaryString.getBytes();
        System.out.println(Arrays.toString(readEncryptedBytesWithIvAndSaltPrefix));

The last print statement returns an array with 48 and 49 numbers.

With this additions, i get Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.


Solution

  • Thanks for your suggestions. I used the Base64 encoding. Here is the working code:

    package com.example.passwordsafe.data;
    
    import com.example.passwordsafe.core.usecases.EncryptionModuleInterface;
    
    import javax.crypto.*;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.KeySpec;
    import java.util.Base64;
    
    public class AESEncryption implements EncryptionModuleInterface {
        private static final int ITERATION_COUNT = 1000000;
        private static final int KEY_LENGTH = 256;
        private static final String PBKDF_ALGORITHM = "PBKDF2WithHmacSHA1";
        private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
        private static final String ALGORITHM = "AES";
    
        @Override
        public String encryptPassword(String password, String masterpassword) {
            byte[] finalCiphertext;
    
            SecureRandom random = new SecureRandom();
            byte[] salt = new byte[16];
            random.nextBytes(salt);
    
            KeySpec spec = new PBEKeySpec(masterpassword.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
    
            try {
                SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALGORITHM);
                byte[] key = factory.generateSecret(spec).getEncoded();
                SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
    
                byte[] ivBytes = new byte[16];
                random.nextBytes(ivBytes);
                IvParameterSpec iv = new IvParameterSpec(ivBytes);
    
                Cipher cipher = Cipher.getInstance(TRANSFORMATION);
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
    
                byte[] inputBytes = password.getBytes();
    
                byte[] encValue = cipher.doFinal(inputBytes);
                finalCiphertext = new byte[encValue.length+2*16];
                System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
                System.arraycopy(salt, 0, finalCiphertext, 16, 16);
                System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);
    
            } catch (NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException |
                     InvalidKeySpecException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException e) {
                throw new RuntimeException(e);
            }
    
            Base64.Encoder encoder = Base64.getEncoder();
    
            return encoder.encodeToString(finalCiphertext);
        }
    
        @Override
        public String decryptPassword(String password, String masterpassword) {
            byte[] ivBytes = new byte[16];
            byte[] salt = new byte[16];
            byte[] encValue;
    
            Base64.Decoder decoder = Base64.getDecoder();
            byte[] readEncryptedBytesWithIvAndSaltPrefix = decoder.decode(password);
    
            byte[] inputBytes = new byte[readEncryptedBytesWithIvAndSaltPrefix.length - 32];
    
            System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 0, ivBytes, 0, 16);
            System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 16, salt, 0, 16);
            System.arraycopy(readEncryptedBytesWithIvAndSaltPrefix, 32, inputBytes, 0, readEncryptedBytesWithIvAndSaltPrefix.length - 32);
    
            KeySpec spec = new PBEKeySpec(masterpassword.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
    
            try {
                SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALGORITHM);
                byte[] key = factory.generateSecret(spec).getEncoded();
                SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
    
                IvParameterSpec iv = new IvParameterSpec(ivBytes);
    
                Cipher cipher = Cipher.getInstance(TRANSFORMATION);
                cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
    
                encValue = cipher.doFinal(inputBytes);
    
            } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                     BadPaddingException | InvalidKeySpecException | InvalidAlgorithmParameterException e) {
                throw new RuntimeException(e);
            }
    
            return new String(encValue);
        }
    }