Leejoy
Leejoy

Reputation: 1446

java.security.InvalidKeyException: Parameters missing

I need to store the encrypted password in DB without storing any key or salt. And I want to make sure it's safer as well even though it's a two-way encryption. So after googling a bit, I have created a sample program to test it. My idea is to create a custom JPA AttributeConverter class to manage this.

Following is the program:

import java.security.Key;
import java.security.spec.KeySpec;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

    private static final String _algorithm = "AES";
    private static final String _password = "_pasword*";
    private static final String _salt = "_salt*";
    private static final String _keygen_spec = "PBKDF2WithHmacSHA1";
    private static final String _cipher_spec = "AES/CBC/PKCS5Padding";

    public static String encrypt(String data) throws Exception {
        Key key = getKey();
        System.out.println(key.toString());
        Cipher cipher = Cipher.getInstance(_cipher_spec);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encVal = cipher.doFinal(data.getBytes());
        String encryptedValue = Base64.getEncoder().encodeToString(encVal);
        System.out.println("Encrypted value of "+data+": "+encryptedValue);
        return encryptedValue;
    }

    public static void decrypt(String encryptedData) throws Exception {
        Key key = getKey();
        System.out.println(key.toString());
        Cipher cipher = Cipher.getInstance(_cipher_spec);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decordedValue = Base64.getDecoder().decode(encryptedData);
        byte[] decValue = cipher.doFinal(decordedValue);
        String decryptedValue = new String(decValue);
        System.out.println("Decrypted value of "+encryptedData+": "+decryptedValue);
    }

    private static Key getKey() throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(_keygen_spec);
        KeySpec spec = new PBEKeySpec(_password.toCharArray(), _salt.getBytes(), 65536, 128);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), _algorithm);
        return secret;
    }

    public static void main(String []str) throws Exception {
        String value = encrypt("India@123");
        decrypt(value);
    }
}

But its throwing the following exception:

javax.crypto.spec.SecretKeySpec@17111
Encrypted value of India@123: iAv1fvjMnJqilg90rGztXA==
javax.crypto.spec.SecretKeySpec@17111
Exception in thread "main" java.security.InvalidKeyException: Parameters missing
    at com.sun.crypto.provider.CipherCore.init(CipherCore.java:469)
    at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:313)
    at javax.crypto.Cipher.implInit(Cipher.java:802)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
    at javax.crypto.Cipher.init(Cipher.java:1249)
    at javax.crypto.Cipher.init(Cipher.java:1186)
    at org.lp.test.Crypto.decrypt(Crypto.java:37)
    at org.lp.test.Crypto.main(Crypto.java:54)

I am not able to figure this out.

I have rectified the exception based on @Luke Park's answer I have created a JPA AttributeConverter as below:

    import java.security.Key;
import java.security.spec.KeySpec;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply=true)
public class CryptoJPAConverter implements AttributeConverter<String, String> {

    private static final String _algorithm = "AES";
    private static final String _password = "_pasword*";
    private static final String _salt = "_salt*";
    private static final String _keygen_spec = "PBKDF2WithHmacSHA1";
    private static final String _cipher_spec = "AES/ECB/PKCS5Padding";

    @Override
    public String convertToDatabaseColumn(String clearText) {
        Key key;
        Cipher cipher;
        try {
            key = getKey();
            cipher = Cipher.getInstance(_cipher_spec);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = cipher.doFinal(clearText.getBytes());
            String encryptedValue = Base64.getEncoder().encodeToString(encVal);
            return encryptedValue;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String convertToEntityAttribute(String encryptedText) {
        Key key;
        try {
            key = getKey();
            Cipher cipher = Cipher.getInstance(_cipher_spec);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = Base64.getDecoder().decode(encryptedText);
            byte[] decValue = cipher.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Key getKey() throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(_keygen_spec);
        KeySpec spec = new PBEKeySpec(_password.toCharArray(), _salt.getBytes(), 65536, 128);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), _algorithm);
        return secret;
    }
}

I have used the two way encryption because I need to pass the password as clear text to Java mail client.

Suggestion and comments are welcome

Upvotes: 3

Views: 4201

Answers (1)

Luke Joshua Park
Luke Joshua Park

Reputation: 9805

You are using AES/CBC/PKCS5Padding but you are not passing an IV to your cipher.init calls.

CBC mode requires a random IV for each encryption operation, you should generate this using SecureRandom each time you encrypt, and pass the value in as an IvParameterSpec. You'll need the same IV for decryption. It is common to prepend the IV to the ciphertext and retrieve when required.

On a seperate note, encrypting passwords is really quite a terrible idea. The fact that you have to ask this question at all somewhat proves that you aren't in a position to be making security-related decisions. Do yourself and your project a favour and hash your passwords instead. PBKDF2 and bcrypt are both decent ways of doing so.

Upvotes: 5

Related Questions