Example: Deriving record specific keys

The following code snippets are actually compiled and run during the BouncyGPG build process. This ensures that all examples are correct.

To derive another key from one (master key) a key derivation function should be used. HKDF defined in RFC5869 is such a function.

Internally HKDF uses an HMAC to derive multiple keys fro one master key.

To quote from RFC5869:

A key derivation function (KDF) is a basic and essential component of cryptographic systems. Its goal is to take some source of initial keying material and derive from it one or more cryptographically strong secret keys.

The following code will derive an AES-128 key and IV (or nonce) for a record identified as version 3 of row #2386221 in MY_TABLE:

import java.security.GeneralSecurityException;
import name.neuhalfen.projects.crypto.symmetric.keygeneration.DerivedKeyGenerator;
import name.neuhalfen.projects.crypto.symmetric.keygeneration.DerivedKeyGeneratorFactory;
import org.bouncycastle.util.encoders.Hex;

// Rather obviously the master key MUST NOT be part of the source code!
// This is only for demonstration purposes!
byte[] masterkey = Hex.decode("81d0994d0aa21b786d6b8dc45fc09f31");

// The salt value should not be part of the source code.
// The same salt value can be reused for all key derivations.
byte[] salt = Hex.decode("b201445d3bcdc7a07c469b7d7ef8988c");

// These settings depend on the algorithms used.
// This combination could e.g. be used for AES-256 in CTR or CBC mode
final int IV_LENGTH_BYTES = 128 / 8;

// This is the length of the generated key.
// For AES-256 use KEY_LENGTH_BYTES = 256/8
final int KEY_LENGTH_BYTES = 128 / 8;

// The key to be generated depends on three things:
//  -  the master key
//  -  the salt value
//  -  the info
// Master key and salt are relatively static and are often scoped "per installation of the application".
//
// The info value must(!) uniquely(!) identify the object to be encrypted.
// It is mandatory that the same key/iv combination is not used multiple times.
// A good way to do this is to use a combination of the objects type, id, and version.
// Version means: "This is incremented every time the record is changed".
// Using a random value (that is refreshed for each update) is also possible.
String context = "MY_TABLE";
String databasePrimaryKey = "2386221";
String recordVersion = "3";

final DerivedKeyGenerator derivedKeyGenerator =
   DerivedKeyGeneratorFactory
       .fromInputKey(masterkey)
       .andSalt(salt)
       .withHKDFsha256();

final byte[] iv = new byte[IV_LENGTH_BYTES];
final byte[] key = new byte[KEY_LENGTH_BYTES];

// The key derivation creates (arbitrary long) streams of "randomness" (it is a PRF - pseudo random function).
// Request enough randomness to cover IV and key
final byte[] keyAndIV = derivedKeyGenerator
   .deriveKey(context, databasePrimaryKey, recordVersion, IV_LENGTH_BYTES + KEY_LENGTH_BYTES);

System.arraycopy(keyAndIV, 0, key, 0, key.length);
System.arraycopy(keyAndIV, key.length, iv, 0, iv.length);

System.out.println("IV:  " + Hex.toHexString(iv));
System.out.println("Key: " + Hex.toHexString(key));

IV: 3af706a49e4d9bb09cbdb01bbfc9d8ff Key: 551cb7df244e577b5b556634117c3895

Example: Changing the version will change the derived key

Changing the version of the record value will derive different keys. The IV for this example is not shown. Also the key length is set to 128 bit.

Context ID Version derived key Remark
MY_TABLE 2386221 1 0xec82fb52017238960175d3a67d8f8f97 This and the following two rows show keys for multiple versions of the same record.
MY_TABLE 2386221 20xe1ba165e7796a4eae010ba90831d00c5 Incrementing the version field will generate different keys.
MY_TABLE 2386221 30x551cb7df244e577b5b556634117c3895 The example shown above.
CarInsuranceContract2386221 3 0x8cf4a7e699f51ad9fa01598898f02052 The same 'row' in different 'tables' yields different keys.
CarInsuranceContract D9FF7A8A-5692-48D7-A4F7-45E149448BBA3 0xb2381876a3b63a1e90f35f8880c6373b IDs just need to be distinct, not necessarily integers.
CarInsuranceContract D9FF7A8A-5692-48D7-A4F7-45E149448BBA 9EBDF712-BBBE-480E-8369-A79F8E653B630x7eed3ff1996d95db97eefcbd55f2d8d3 Versions just need to be distinct, not necessarily integers.
CarInsuranceContract D9FF7A8A-5692-48D7-A4F7-45E149448BBA 570E7AA2-EC6A-4F14-A58E-BB3E7E671FED0x2e7bc71432f52b1a868553de401c4f84