How to derive an AES 256 bit key from an arbitary byte array it Java?

Viewed 721

Given an arbitrary Java byte array for example 1024 byte array I would like to derive an AES-256 bit key. The array is generated from ECHD via javax.crypto.KeyAgreement using byte[] secret = keyAgreement.generateSecret()

My current solution is to treat the input byte array as a password. Use the PBKDF2 key derivation function the input array as both the password and the salt as shown below.

UPDATE: I have set UTF-8 as the encoding to address issues pointed out in comments and answers.

private byte[] deriveAes256bitKey(byte[] secret)
    throws NoSuchAlgorithmException, InvalidKeySpecException {

    var secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    var password = new String(secret, UTF_8).toCharArray();
    var keySpec = new PBEKeySpec(password, secret, 1024, 256);
    return secretKeyFactory.generateSecret(keySpec).getEncoded();
}

Is there a better way to take a byte array in Java and turn it into an AES-256 bit key?

2 Answers

I would be wary of using new String(input).toCharArray() to create the password. It's not portable (it uses the platform default encoding), and its behaviour is undefined if there are invalid character sequences in the input.

Consider this:

    System.out.println(new String(new byte[] {(byte) 0xf0, (byte) 0x82, (byte) 0x82, (byte) 0xac}, StandardCharsets.UTF_8));

f08282ac is an over long encoding of the Euro sign (€). It's decoded to the replacement character (�; 0xfffd) because it's an illegal sequence. All illegal UTF-8 sequences will end up as the replacement char, which is not what you want.

You could avoid decoding problems by serialising the byte array before passing it to the SecretKeyFactory (base64 encode it, or simply new BigInteger(input).toString(Character.MAX_RADIX)). However, this can be avoided if you don't use the SecretKeyFactory. It's unnecessary.

PBKDF2 (Password-Based Key Derivation Function 2) is designed to make brute force attacks against user supplied passwords harder by being computationally expensive and adding salt.

You don't need that here (your input is large and random; nobody will be mounting dictionary attacks against it). Your problem is just that the input length doesn't match the required key length.

You can just hash the input to the correct length:

MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = md.digest(input);

What is required here is a KBKDF or Key Based Key Derivation Function. A KBKDF converts a secret value that contains enough entropy into a different key of a specific size. A PBKDF is used when you have a passphrase with potentially too little entropy into a key using key strenghtening (using the salt and work factor or iteration count). The work factor / iteration count doesn't need to be used if the input value is already strong enough not to be guessed / brute forced.

SHA-256 in general suffices if you only want a resulting 128 bit value. However, using a key derivation function may still offer benefits. First of all, it is a function that is explicitly defined for the function, so it is easier to prove that it is secure. Furthermore, it is generally possible to add additional data to the key derivation function so that you can e.g. derive more keys or a key and an IV. Or you can expand the configurable output size to output enough data for different keys or key / IV.

That said, most cryptographers won't frown too much if you use SHA-256 (or SHA-512 in case you require more bits for key / IV). The output is still supposed to be randomized using all possible bits from the input, and it is impossible to inverse the function.

Related