2

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?

ams
  • 60,316
  • 68
  • 200
  • 288
  • 3
    How much entropy is in your array? You should use an established key derivation function (KDF). Which one depends on how much entropy is in the array but I believe the only good one available in Java is PBKDF2. PBKDF2 contains extra security features to enhance the security margins for low-entropy values like passwords. Those features won't hurt you if you have a full-entropy array however. As the one answer notes you should not convert your array into a String in the manner you are doing. You should instead base64 encode it. If you have the Bouncycastle library you have other options for KDF. – President James K. Polk Oct 06 '20 at 13:26
  • 1
    One option could be to use PBKDF using a single iteration. But because of the weird string handling the Java PBKDF API, I would not recommend that. – Maarten Bodewes Oct 07 '20 at 10:51
  • @MaartenBodewes I updated the sample code above to use UTF_8 does not make a difference for the wierd string handling? Can you elaborate more on what the impact of the weird string handling is? – ams Oct 07 '20 at 16:59

2 Answers2

3

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);
teppic
  • 7,051
  • 1
  • 29
  • 35
  • 3
    What really is required is a KBKDF, a **Key Based** Key Derivation Function. SHA-256 can be seen as a "poor man's KDF". Unfortunately Java doesn't include KBKDF's by by default, you can vote for the API enhancement [here](https://bugs.openjdk.java.net/browse/JDK-8189808). I've implemented the initial version of HKDF and a few others for the Bouncy Castle crypto library. But if you just require a single key then SHA-256 suffices *in practice*. – Maarten Bodewes Oct 07 '20 at 10:13
  • @MaartenBodewes can you expand in an answer on what the choices are and what the pros and cons of using SHA-256 vs. KBKDF what would a KBKDF do that makes it better than SHA-256? and maybe how a KBKDF is different than PBKDF what is the difference between a password and a Key if both are kept secret are they not the same thing. – ams Oct 07 '20 at 16:56
  • Well, OK, but I think the current answer is good enough. BWT, questions such as these are often more on topic on IT security or the cryptography sites of stack exchange. – Maarten Bodewes Oct 10 '20 at 19:33
1

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263