Better Way to Create Aes Keys Than Seeding Securerandom

Better way to create AES keys than seeding SecureRandom

No, the whole idea that you should use a SecureRandom for key derivation from static data is rather bad:

  1. SecureRandom's main function is to generate random values, it should not be used as a generator for a key stream;
  2. SecureRandom, when instantiated with "SHA1PRNG" does not implement a well defined algorithm, and the algorithm has actually be known to change, even from one Sun JDK to another;
  3. The Oracle provided implementation of "SHA1PRNG" uses the initial seed as only seed, others may just add the seed to the random pool.

Using "SHA1PRNG" as key derivation function has been known to produce issues on several versions of Android, and may fail on any other Java RE.


So what should you do instead?

  1. Use new SecureRandom() or even better, KeyGenerator to generate a truly random key, without seeding the random number generator if you need a brand new random key;
  2. Directly provide a byte[] of a known key to SecretKeySpec, or use a hexadecimal decoder to decode it from hexadecimals (note that String instances are hard to delete from memory, so only do this if there is no other way);
  3. Use PBKDF2 if you want to create a key from a password (use a higher iteration count than the one provided in the link though);
  4. Use a true Key Based Key Derivation Mechanism if you want to create multiple keys from one key seed, e.g. use HKDF (see below).

Option 4 would be preferred if the seed was generated by e.g. a key agreement algorithm such as Diffie-Hellman or ECDH.


Note that for option 3, PBKDF2, you would be wise to keep to ASCII passwords only. This is due to the fact that the PBKDF2 implementation by Oracle does not use UTF-8 encoding.


As for option 4, I've helped with adding all good KBKDF's to the Bouncy Castle libraries so there isn't a need to implement a KBKDF yourself if you can add Bouncy Castle to your classpath and/or list of installed security providers. Probably the best KBKDF at the moment is HKDF. If you cannot add Bouncy Castle to your classpath then you might want to use the leftmost bytes of SHA-256 output over the derivation data as a "poor man's" KDF.

AES Encryption IV's

An AES key simply consists of random bytes. For CBC mode the IV mode should also be randomized (at least to an attacker). So in general you can simply use a SecureRandom instance to create the key and IV. The IV can then be included with the ciphertext; usually it is simply put in front of it.

With Java it is better to use a KeyGenerator though. If you look at the implementation of it in the SUN provider it will probably amount to the same thing. However using a KeyGenerator is more compatible with various kinds of keys and providers. It may well be that it is a requirement for generating keys in e.g. smart cards and HSM's.

So lets show a class with three simple methods:

package nl.owlstead.stackoverflow;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Optional;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class CreateKeyAndIVForAES_CBC {

public static SecretKey createKey(final String algorithm, final int keysize, final Optional<Provider> provider, final Optional<SecureRandom> rng) throws NoSuchAlgorithmException {
final KeyGenerator keyGenerator;
if (provider.isPresent()) {
keyGenerator = KeyGenerator.getInstance(algorithm, provider.get());
} else {
keyGenerator = KeyGenerator.getInstance(algorithm);
}

if (rng.isPresent()) {
keyGenerator.init(keysize, rng.get());
} else {
// not really needed for the Sun provider which handles null OK
keyGenerator.init(keysize);
}

return keyGenerator.generateKey();
}

public static IvParameterSpec createIV(final int ivSizeBytes, final Optional<SecureRandom> rng) {
final byte[] iv = new byte[ivSizeBytes];
final SecureRandom theRNG = rng.orElse(new SecureRandom());
theRNG.nextBytes(iv);
return new IvParameterSpec(iv);
}

public static IvParameterSpec readIV(final int ivSizeBytes, final InputStream is) throws IOException {
final byte[] iv = new byte[ivSizeBytes];
int offset = 0;
while (offset < ivSizeBytes) {
final int read = is.read(iv, offset, ivSizeBytes - offset);
if (read == -1) {
throw new IOException("Too few bytes for IV in input stream");
}
offset += read;
}
return new IvParameterSpec(iv);
}

public static void main(String[] args) throws Exception {
final SecureRandom rng = new SecureRandom();
// you somehow need to distribute this key
final SecretKey aesKey = createKey("AES", 128, Optional.empty(), Optional.of(rng));
final byte[] plaintext = "owlstead".getBytes(UTF_8);

final byte[] ciphertext;
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream();

final Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
final IvParameterSpec ivForCBC = createIV(aesCBC.getBlockSize(), Optional.of(rng));
aesCBC.init(Cipher.ENCRYPT_MODE, aesKey, ivForCBC);

baos.write(ivForCBC.getIV());

try (final CipherOutputStream cos = new CipherOutputStream(baos, aesCBC)) {
cos.write(plaintext);
}

ciphertext = baos.toByteArray();
}

final byte[] decrypted;
{
final ByteArrayInputStream bais = new ByteArrayInputStream(ciphertext);

final Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
final IvParameterSpec ivForCBC = readIV(aesCBC.getBlockSize(), bais);
aesCBC.init(Cipher.DECRYPT_MODE, aesKey, ivForCBC);

final byte[] buf = new byte[1_024];
try (final CipherInputStream cis = new CipherInputStream(bais, aesCBC);
final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int read;
while ((read = cis.read(buf)) != -1) {
baos.write(buf, 0, read);
}
decrypted = baos.toByteArray();
}
}

System.out.println(new String(decrypted, UTF_8));
}
}

Note that you may not always want to generate and distribute an AES key "out-of-band". Here are a few other methods of generating a key (part #2 onwards). You may also want to take a look at more advanced exception handling for the cryptographic operation.

Image encryption/decryption using AES256 symmetric block ciphers

Warning: This answer contains code you should not use as it is insecure (using SHA1PRNG for key derivation and using AES in ECB mode)

Instead (as of 2016), use PBKDF2WithHmacSHA1 for key derivation and AES in CBC or GCM mode (GCM provides both privacy and integrity)

You could use functions like these:

private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}

private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}

And invoke them like this:

ByteArrayOutputStream baos = new ByteArrayOutputStream();  
bm.compress(Bitmap.CompressFormat.PNG, 100, baos); // bm is the bitmap object
byte[] b = baos.toByteArray();

byte[] keyStart = "this is a key".getBytes();
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(keyStart);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();

// encrypt
byte[] encryptedData = encrypt(key,b);
// decrypt
byte[] decryptedData = decrypt(key,encryptedData);

This should work, I use similar code in a project right now.

Is a SecureRandom really needed for generating Initialization vectors or is Random enough?

The biggest problem with using Random to generate your IV is not that it is likely to repeat, but that an attacker can predict future IVs, and this can be used to attack CBC.

Related: https://crypto.stackexchange.com/q/3515/2805

Generate AES key without password using BouncyCastle

As long as you are just using AES, you can get away with just building a KeyParameter class directly. However, there are symmetric algorithms with classes of known weak keys and/or other restrictions on what is a valid key, e.g. DESEDE.

If your code needs to handle multiple algorithms (or modes) generically, then you will be better off using Org.BouncyCastle.Security.GeneratorUtilites to get an appropriate key generator for the algorithm. Likewise, ParameterUtilities is preferred in the general case e.g. for adding an IV.

Likewise the Java code you gave will work OK for AES, but if you want to generalise across ciphers and modes, you ought to be using the KeyGenerator and AlgorithmParameterGenerator APIs.



Related Topics



Leave a reply



Submit