0

I am using below code for encryption & decryption in Spring boot project with conveter annotation on attributes which I want to encrypt & decrypt

import org.apache.commons.codec.binary.Base64;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;

@Converter
public class CryptoConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String attribute) {
        if(attribute == null){
            return null;
        }
        try {
        byte[] ivBytes;
        //String password="Hello";
        String password = EncryptionUtil.key.get();
        SecureRandom random = new SecureRandom();
        byte bytes[] = new byte[20];
        random.nextBytes(bytes);
        byte[] saltBytes = bytes;
// Derive the key
        SecretKeyFactory factory = null;

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(),saltBytes,65556,256);
        SecretKey secretKey = factory.generateSecret(spec);
        SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

            //encrypting the word
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);
            AlgorithmParameters params = cipher.getParameters();
            ivBytes =   params.getParameterSpec(IvParameterSpec.class).getIV();
            byte[] encryptedTextBytes =  cipher.doFinal(attribute.getBytes("UTF-8"));
            //prepend salt and vi
            byte[] buffer = new byte[saltBytes.length + ivBytes.length + encryptedTextBytes.length];
            System.arraycopy(saltBytes, 0, buffer, 0, saltBytes.length);
            System.arraycopy(ivBytes, 0, buffer, saltBytes.length, ivBytes.length);
            System.arraycopy(encryptedTextBytes, 0, buffer, saltBytes.length + ivBytes.length, encryptedTextBytes.length);
            return new Base64().encodeToString(buffer);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidParameterSpecException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {

        if(dbData == null){
            return null;
        }

        try {
            String password = EncryptionUtil.key.get();
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        //strip off the salt and iv
        ByteBuffer buffer = ByteBuffer.wrap(new Base64().decode(dbData));
        byte[] saltBytes = new byte[20];
        buffer.get(saltBytes, 0, saltBytes.length);
        byte[] ivBytes1 = new byte[cipher.getBlockSize()];
        buffer.get(ivBytes1, 0, ivBytes1.length);
        byte[] encryptedTextBytes = new byte[buffer.capacity() - saltBytes.length - ivBytes1.length];

        buffer.get(encryptedTextBytes);
        // Deriving the key
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, 65556, 256);
        SecretKey secretKey = factory.generateSecret(spec);
        SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes1));
        byte[] decryptedTextBytes = null;

            decryptedTextBytes = cipher.doFinal(encryptedTextBytes);


        return new String(decryptedTextBytes);

        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return null;
    }
}

The issue is this code takes a lot of time to encrypt & decrypt.

With this I am getting a serious performance hit.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ashish
  • 1,856
  • 18
  • 30
  • have you profiled the code to see where it is spending all the time? – jtahlborn Jan 26 '18 at 14:43
  • 1
    The code was perhaps copy&pasted from somewhere and you have no idea what it does or why? You say it takes a longer time, but longer than *what*? You do have a PBE scheme which uses iterations to slow the work of an attacker, but it also makes your code work harder. That's by design. – President James K. Polk Jan 26 '18 at 19:10
  • I see you have some trouble handling your exceptions, try reading [this answer](https://stackoverflow.com/a/15712409/589259); don't forget to upvote if it is useful to you. The general use of cryptographic primitives is OK, although you may want to use AES/GCM/NoPadding instead of CBC mode if you want to have more security. – Maarten Bodewes Jan 26 '18 at 22:31

1 Answers1

1

If you want to encrypt faster than simply use an actual randomly generated key instead of a password. You can store that in a KeyStore instance that you protect with a password if you like.

You could also simply move the PBKDF2 key derivation out of the encryption method itself and store the resulting SecretKey instance in a field if and only if the password doesn't change in between calls. That way you only have to derive the key once.

PBKDF2 currently uses 65556 iterations each time you want to encrypt/decrypt anything. And PBKDF2 consists of a single hash - in this case SHA-1 - taking 64 bytes at a time. So even before you start encrypting you've hashed at least 65556 (or 64Ki + 20 for some reason) times 64 bytes. That's 4 MiB of hashing you're doing before starting to encrypt. Compared to that you won't even notice the AES encryption.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263