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.
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);
}
}