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
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;
}
}
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.
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.
If you're just transmitting data and need it to be encrypted in flight, use TLS/SSL. Its way faster and won't break.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With