I've done some reading about implementing AES256 and deriving a key from a password. If I understand correctly:
- I want to generate a new salt (for the key) and a new IV (for the encrypted message) for every new message.
- It should also not be a problem sending the salt and the IV together with the message.
I decided to pack everything together in one byte array where the first 16 bytes is the salt, the next 16 bytes is the IV and the rest is the encrypted message. This with a key length of 256 bits and 20000 iterations for generating the key. I also encode the whole thing in Base64 for transmission.
Can this approach be improved? Knowing that I'm limited to 256 bytes for the complete message (salt+iv+message). How secure is this?
(Feedback and changes at the bottom)
public class app {
public static void main(String[] args) throws Exception {
int iterations = 20000;
int keyLength = 32;
byte[] salt = getRandomBytes(16);
byte[] iv = getRandomBytes(16);
char[] password = "password_here".toCharArray();
byte[] payload = "payload_here".getBytes();
byte[] key = deriveKey(iterations, keyLength, salt, iv, password);
byte[] encrypted = encrypt(iv, key, payload);
byte[] bytes = concatBytes(salt, iv, encrypted);
String output = Base64.encodeBytes(bytes);
int outputLength = output.getBytes().length;
System.out.println(outputLength + "\n" + output);
}
private static byte[] deriveKey(int iterations, int length, byte[] salt, byte[] iv, char[] password)
throws InvalidKeySpecException, NoSuchAlgorithmException {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, length * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] key = skf.generateSecret(spec).getEncoded();
return key;
}
private static byte[] getRandomBytes(int length) throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[length];
sr.nextBytes(salt);
return salt;
}
private static byte[] concatBytes(byte[]... arrays) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (byte[] array : arrays)
outputStream.write(array);
return outputStream.toByteArray();
}
private static byte[] encrypt(byte[] iv, byte[] key, byte[] text) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException {
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec newKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return cipher.doFinal(text);
}
}
Based on feedback I've made the following changes:
- Implemented authenticated encryption by using GCM. I had to switch to the Bouncy Castle library to be able to use GCM.
- I dropped the IV and am using the same bytes for the key salt and the GCM nonce.
- Upped the number of iterations for deriving the key to 50k.
Resulting code:
public class app {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
int iterations = 50000;
int keyLength = 32;
byte[] salt = getRandomBytes(16);
char[] password = "password_here".toCharArray();
byte[] payload = "payload_here".getBytes();
byte[] key = deriveKey(iterations, keyLength, salt, password);
byte[] encrypted = encrypt(salt, key, payload);
byte[] bytes = concatBytes(salt, encrypted);
String output = Base64.encodeBytes(bytes);
int outputLength = output.getBytes().length;
System.out.println(outputLength + "\n" + output);
}
private static byte[] deriveKey(int iterations, int length, byte[] salt, char[] password)
throws InvalidKeySpecException, NoSuchAlgorithmException {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, length * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return skf.generateSecret(spec).getEncoded();
}
private static byte[] getRandomBytes(int length) throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] bytes = new byte[length];
sr.nextBytes(bytes);
return bytes;
}
private static byte[] concatBytes(byte[]... arrays) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (byte[] array : arrays)
outputStream.write(array);
return outputStream.toByteArray();
}
private static byte[] encrypt(byte[] nonce, byte[] key, byte[] text) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, IllegalStateException, InvalidCipherTextException {
GCMBlockCipher gcm = new GCMBlockCipher(new AESFastEngine());
AEADParameters params = new AEADParameters(new KeyParameter(key), 128, nonce);
gcm.init(true, params);
byte[] output = new byte[gcm.getOutputSize(text.length)];
int len = gcm.processBytes(text, 0, text.length, output, 0);
gcm.doFinal(output, len);
return output;
}
}