Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can i avoid the cipher reinitialization per encrypt/decrypt call when using random salts per encryption?

Edit

Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.

Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE api is kind of misleading depending on the chosen algorithm

Original post

As cryptography in Java is quite... cryptographic, im struggling to do some optimizations. In the functional aspect, this class works quite well and i hope it serves as an example of AES encryption usage

I have a performance issue when encrypting and decrypting data using AES implementation of BouncyCastle (im not comparing, thats the only one implementation I tested). Actually this problem is generic to any cipher I decide to use.

The main issue is: can i avoid the two ciphers whole re-initialization per encrypt/decrypt call? They are too expensive

For the sake of simplicity, keep in mind that the following code had its exception handling removed and a lot of simplification was made to keep the focus on the problem. The synchronized blocks are there to guarantee thread safety

By the way, feedbacks on any part of the code are welcome

Thx

import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

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

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class AES {

    private static final int ITERATIONS = 120000;
    private static final int SALT_SIZE_IN_BYTES = 8;
    private static final String algorithm = "PBEWithSHA256And128BitAES-CBC-BC";
    private static final byte[] KEY_SALT = "a fixed key salt".getBytes(Charset.forName("UTF-8"));

    private Cipher encryptCipher;
    private Cipher decryptCipher;
    private SecretKey key;
    private RandomGenerator randomGenerator = new RandomGenerator();

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
            Security.addProvider(new BouncyCastleProvider());
    }

    public AES(String passphrase) throws Exception {
        encryptCipher = Cipher.getInstance(algorithm);
        decryptCipher = Cipher.getInstance(algorithm);
        PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), KEY_SALT, ITERATIONS);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
        key = keyFactory.generateSecret(keySpec);
    }

    public byte[] encrypt(byte[] data) throws Exception {
        byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
        data = DataUtil.append(data, salt);

        byte[] encrypted;
        synchronized (encryptCipher) {
            // as a security constrain, it is necessary to use different salts per encryption
            // core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
            encryptCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, parameterSpec);
            encrypted = encryptCipher.doFinal(data);
        }
        return DataUtil.append(encrypted, salt);
    }

    public byte[] decrypt(byte[] data) throws Exception {
        byte[] salt = extractSaltPart(data);
        data = extractDataPart(data);

        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);

        byte[] decrypted;

        synchronized (decryptCipher) {
            // as a security constrain, it is necessary to use different salts per encryption
            // core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
            decryptCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, parameterSpec); 
            decrypted = decryptCipher.doFinal(data);
        }

        byte[] decryptedSalt = extractSaltPart(decrypted);

        if (Arrays.equals(salt, decryptedSalt))
            return extractDataPart(decrypted);
        else
            throw new IllegalArgumentException("Encrypted data is corrupted: Bad Salt");
    }

    protected byte[] extractDataPart(byte[] bytes) {
        return DataUtil.extract(bytes, 0, bytes.length - SALT_SIZE_IN_BYTES);
    }

    protected byte[] extractSaltPart(byte[] bytes) {
        return DataUtil.extract(bytes, bytes.length - SALT_SIZE_IN_BYTES, SALT_SIZE_IN_BYTES);
    }

    // main method to basic check the code execution
    public static void main(String[] args) throws Exception {
        String plainText = "some plain text, have fun!";
        String passphrase = "this is a secret";

        byte[] data = plainText.getBytes(Charset.forName("UTF-8"));

        AES cipher = new AES(passphrase);
        byte[] encrypted = cipher.encrypt(data);
        byte[] decrypted = cipher.decrypt(encrypted);

        System.out.println("expected: true, actual: " + Arrays.equals(data, decrypted));
    }
}

// Utility class
class RandomGenerator {

    private SecureRandom random = new SecureRandom();

    public RandomGenerator() {
        random = new SecureRandom();
        random.nextBoolean();
    }

    public synchronized byte[] generateRandom(int length) {
        byte[] data = new byte[length];
        random.nextBytes(data);
        return data;
    }
}

// Utility class
class DataUtil {

    public static byte[] append(byte[] data, byte[] append) {
        byte[] merged = new byte[data.length + append.length];
        System.arraycopy(data, 0, merged, 0, data.length);
        System.arraycopy(append, 0, merged, data.length, append.length);
        return merged;
    }

    public static byte[] extract(byte[] data, int start, int length) {
        if (start + length > data.length)
            throw new IllegalArgumentException("Cannot extract " + length + " bytes starting from index " + start + " from data with length " + data.length);

        byte[] extracted = new byte[length];
        System.arraycopy(data, start, extracted, 0, length);
        return extracted;
    }

}
like image 671
Bruno Penteado Avatar asked Sep 20 '12 23:09

Bruno Penteado


3 Answers

You're out of luck. If you're picking a new salt each time, that means you're using a new key for encryption/decryption each time, which means you need to call init each time.

If you want something faster, just salt your message:

byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
encryptCipher.update(salt);
encrypted = encryptCipher.doFinal(data);

That way, you use the same key every time so you don't need to reinitialize it. (Don't use PBE, just use 128 bit AES/CBC). It's hard to know if that is adequate for your needs without knowing how you plan to apply this encryption in the real world.

p.s. ITERATIONS == 120000? No wonder it is so slow.

like image 179
Keith Randall Avatar answered Nov 05 '22 11:11

Keith Randall


Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.

Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE API is kind of misleading depending on the chosen algorithm

About the salt: Adding a salt to the plain message is completely unnecessary. What I really should use to achieve randomness in each encryption is using a random Initialization Vector, that can be appended or prepended to the ciphertext after the encryption just like the salt.

like image 28
Bruno Penteado Avatar answered Nov 05 '22 09:11

Bruno Penteado


  1. If you're just transmitting data and need it to be encrypted in flight, use TLS/SSL. Its way faster and won't break.

  2. Make Sure you use some authentication on your cipher-text. Either use a MAC or better use AES in GCM or CCM modes. Otherwise you encryption is insecure.

As to your question : yes its fixable. Just generate the key once and reuse it/ . AES is safe to use to send multiple messages with the same key. So just derive the key/Cipher once and keep using it.

Just make sure you use a fresh IV for each message.

like image 29
imichaelmiers Avatar answered Nov 05 '22 09:11

imichaelmiers