0

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?

1
  • 1
    Your decryption code doesn't make much sense. You generate a random salt and a random IV during decryption. Instead, the salt and IV used for decryption must be the same as those used for encryption. In addition, decryption does not require concatenation (on the contrary, the concatenated data must be separated here). Your doCrypto() method is currently only tailored to encryption. You need a separate method for decryption (or a case distinction). Commented Jan 17 at 12:41

1 Answer 1

2

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.