I would like to implement a password safe in java. I have an EncryptionModule
with two methods void encrypt(String password, String databaseName)
and void decrypt(String password, String databaseName)
. The methods take the password and filename provided by the user. It should be possible to encrypt/ decrypt a csv file or a mysql database.
When I execute the code I get the following error message:
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
The Exception comes from the decrypt method.
The code of the EncryptionModule:
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.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
public class EncryptionModule implements EncryptionModuleInterface {
private static final String PBKDF_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM = "AES";
@Override
public void encrypt(String password, String databaseName) {
Path path = Paths.get(databaseName);
if (Files.exists(path)){
File plaintextFile = new File(databaseName);
File encryptedFile = new File(databaseName + ".encrypted");
doCrypto(Cipher.ENCRYPT_MODE, password, plaintextFile, encryptedFile);
} else {
System.out.println("File does not exist");
}
}
@Override
public void decrypt(String password, String databaseName) {
Path path = Paths.get(databaseName);
if (Files.exists(path)){
File encryptedFile = new File(databaseName);
File plaintextFile = new File(databaseName + ".decrypted");
doCrypto(Cipher.DECRYPT_MODE, password, encryptedFile, plaintextFile);
} else {
System.out.println("File does not exist");
}
}
private void doCrypto (int cipherMode, String password, File inputFile, File outputFile) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000000, 256);
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(cipherMode, keySpec, iv);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int) inputFile.length()];
inputStream.read(inputBytes);
byte[] encValue = cipher.doFinal(inputBytes);
byte[] 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);
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(finalCiphertext);
inputStream.close();
outputStream.close();
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
IOException | BadPaddingException | InvalidKeySpecException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
}
The methods are called in main:
package com.example.passwordsafe.presentation.terminal;
import com.example.passwordsafe.core.usecases.*;
import com.example.passwordsafe.data.EncryptionModule;
import com.example.passwordsafe.data.FileAccess;
import com.example.passwordsafe.passwordgenerator.PasswordGenerator;
import java.io.File;
public class MainTerminal {
public static void main(String[] args) {
// Create CSV file
String fileName = "../passwords.csv";
new File(fileName);
// Create objects
OutputInterface output = new TerminalOutput();
DataAccessInterface dataAccessInterface = new FileAccess(fileName);
PasswordGeneratorInterface passwordGenerator = new PasswordGenerator();
EncryptionModuleInterface encryptionModule = new EncryptionModule();
InputInterface inputInterface = new UseCases(output, dataAccessInterface, passwordGenerator, encryptionModule);
UseCasesInterface useCasesInterface = new UseCases(output, dataAccessInterface, passwordGenerator, encryptionModule);
TerminalInput terminalInput = new TerminalInput(inputInterface, useCasesInterface);
// Do Stuff
// Encryption Module Test
String password = "masterpassword";
encryptionModule.encrypt(password,fileName);
encryptionModule.decrypt(password, "../passwords.csv.encrypted");
}
}
At first I used the code from: https://www.codejava.net/coding/file-encryption-and-decryption-simple-example, but I gave me the exception: java.security.InvalidKeyException: Invalid AES key length
. Then I tried to include the code from Artjom B.s Answer here: How to fix Invalid AES key length?. Now I have the Exception mentioned above.
What can I do to fix the exception?
You are adding the following to the ciphertext in the file but not using and removing it for the decrypt step. That changes the ciphertext to something with the wrong length/padding for AES.
byte[] 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);
Your decrypt needs to use the written iv and salt to re-generate the key and decrypt like this:
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] readEncyptedBytesWithIvAndSaltPrefix = new byte[(int) inputFile.length()];
inputStream.read(readEncyptedBytesWithIvAndSaltPrefix);
System.arraycopy(readEncyptedBytesWithIvAndSaltPrefix, 0, ivBytes, 0, 16);
System.arraycopy(readEncyptedBytesWithIvAndSaltPrefix, 16, salt, 0, 16);
System.arraycopy(readEncyptedBytesWithIvAndSaltPrefix, 32, inputBytes, 0, readEncyptedBytesWithIvAndSaltPrefix.length - 32);
// use the ivBytes & salt to setup key and cipher
// then decrypt
byte[] encValue = cipher.doFinal(inputBytes);