This repository provides an inexpensive means to protect cryptographic master keys (key encryption keys, KEKs) in a way that is resistant to some of the most common remote file exfiltration attacks.
Guidance for generating and managing application Master Keys
Custom KeyStore implementations for Master Keys
Why a Master Key?
Sample code for generating and managing application master keys
First, a few definitions:
- login passwords
- A password or passphrase used by a human to access a front-end system. Store as a randomly-salted hash.
- credentials
- A username and password pair used by a system to access other resources. Must be encrypted.
- Data Encryption Key (DEK)
- A cryptographic key used to encrypt secrets. These secrets may be anything, including cryptographic keys.
- Key Encryption Key (KEK) or "Master Key"
- The top-level cryptographic key used to protect a Data Encryption Key
The guidance given here deals primarily with credentials rather than with ordinary user login passwords.
-
Cryptographic keys (including TLS certificates) and passwords should be unique for each appliance instance and for each on-premises installation. If multiple customers can share the same appliance or server, each must have a unique set of keys and certificates. Otherwise, every legitimate customer will have access to certificates/keys that allow her to spy on all other customers.
-
Keys, certs, and passwords should be established at installation time (first bootup for appliances). Either ask the installer to specify a password (for example, for an admin account password), or generate them dynamically (keystore passwords, cryptography keys, master key, etc.). There should be no default passwords. Customers can easily forget or neglect to change them.
-
Keys and credentials should not be stored in clear text in any configuration files or source code files. If these must be stored, they must be encrypted. Obfuscating or encoding the data alone (by XORing with some static byte array, or Base64 encoding, for example) is not enough. Keys and certificates should be stored in a key store, protected by a long random passphrase or encrypted by a strong randomly generated encryption key.
-
These random keys and passphrases should be generated dynamically at installation time, and they should, themselves, be protected by a randomly generated key. This is often termed the 'master key' because it is used to secure all other cryptographic keys and passwords.
-
Master keys should be generated using a key stretching algorithm like HKDF from data gathered from multiple sources. At least one of these sources may be tied to the hardware on which the system is running. Best practices would NOT store this master key anywhere. It would be re-generated at each system startup from the fixed multiple data sources, some random, some machine-specific.
-
Note that depending on the method by which you gather machine-specific data to create a master key, you may need to create a method whereby the master key can be recorded and placed in a safe place immediately after installation. If you do this, do not forget to also create a means whereby the system can be restarted from the master key if there is some sort of equipment failure.
To make the storage and retrieval of a Master Key simpler, we have provided a couple of implementations of the standard Java KeyStore
class. These store one or more SecretKey
, which you then can use to securely encrypt a Data Encryption Key, which in turn protects all other credentials, keys, and certificates. We recommend this two-tier approach so that the Master Key can easily be backed up, restored, and replaced in case of a breach, without needing to touch any other encrypted data.
The Master Key (loaded and stored via our KeyStore implementations) is used to securely encrypt the Data Encryption Key in a file (this way you have complete understanding and control of the encryption of this important key). The Data Encryption Key is then used to generate any needed passphrases or cryptography keys, which are used to protect credentials, private keys, or other secret encryption keys. To replace the Master Key, you decrypt the Data Encryption Key using the old Master Key, generate a new Master Key, then encrypt the Data Encryption Key (without changing it) with the new Master Key and save the encrypted result.
A few big reasons:
- If you have to change an encryption key for some reason (if was compromised or expired, for example), then with a master key, it's quite simple -- decrypt the data encryption key, change the master key, re-encrypt the data encryption key using the new master key. DONE.
- Offline backup? Simple
- Restore due to a disaster (somebody trashed the master key store somehow)? Simple. It's just one piece of data.
For any of the cases above, if you had only a data encryption key, you would have a MUCH bigger problem. You would have to find each and every piece of encrypted data, decrypt and re-encrypt each piece. That is very costly, time-consuming, and error-prone.
- Using a master key is standard industry practice.
- If your application already exists today, and you have a data encryption key, then introducing a master key is a relatively simple way to improve the protection of your data encryption key (especially if it is currently obfuscated or in the clear). You only need to add the master key keystore, securely encrypt the data encryption key, and you're done. Very little code is touched.
- Upgrading to use of a HSM or other more-sophisticated mechanism (because your application is now wildly popular and you have all the money you need) is quite simple. Just swap out the existing master key code with your new code. Very little code disruption because all changes are isolated to the master key section.
Initialize the system at install time.
- Create and save the Master Key material. There are a couple of possible approaches:
- Use Java's
KeyGenerator
to create a strong AES encryption key, then use ourKeyStore
to save it in a known folder. Note that any files used to store key data has a random name to make exfiltration less likely. Additionally, ourTimestampKeyStore
keeps key data in file metadata, which is not retrievable via remote exfiltration attacks. Be sure to secure the folder's permissions so only the application has permission to enter the folder. - Using a secure random number generator, save random bytes into a series of files with (Base64-encoded) random names, and set the least significant nybble of their timestamps to random data. Save the files in a known protected folder. Sorting the files in alphabetical order, digest their contents, names, and random timestamp nybbles to create a random Master Key.
- Use Java's
- Back up the Master Key value to a file. Use password based encryption to protect it, and have the user keep it offline.
- Generate a secure random Data Encryption Key (DEK) and use the Master Key (KEK in diagram above) to encrypt and save it. You can now wipe the Master Key from memory -- it is only used to encrypt/decrypt the Data Encryption Key.
- Use the DEK and hard-coded alias names to generate any needed KeyStore passphrases, passphrases for each entry in each KeyStore, and encryption keys to encrypt any secret data (like credentials) stored in configuration files.
Because all passphrases and encryption keys can be re-generated dynamically from just the DEK and the correct alias, none need to be stored in the system at all.
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.KeyStore.LoadStoreParameter;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.ibm.eht.sidekek.SecretFolderKeyStoreSpi;
import com.ibm.eht.sidekek.SideKEK;
import java.io.File;
import java.security.Key;
import java.security.KeyStore;
public class InitMasterKey {
private static File getInstallConfigFolder() { // re-write for your app
return new File("./config");
}
private static SecretKey generateMasterKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = null;
keyGen = KeyGenerator.getInstance("AES"); // AES key generator
keyGen.init(256); // 256 bit key
SecretKey masterKey = keyGen.generateKey();
return masterKey;
}
private static void storeMasterKey(File configFolder, Key masterKey) throws Exception {
File keyStoreFolder = new File(configFolder, "keyStore");
KeyStore ks = KeyStore.getInstance(SideKEK.SECRET_FOLDER_KEYSTORE);
LoadStoreParameter param = new SecretFolderKeyStoreSpi.SecretFolderKeyStoreParameter(keyStoreFolder);
ks.load(param);
ks.setKeyEntry("masterKey", masterKey, null, null);
ks.store(param);
}
public static void main(String args[]) throws Exception {
// Initialize the provider
Security.addProvider(new SideKEK());
// We start by generating a new master key (KEK)
SecretKey masterKey = generateMasterKey();
// ...and storing it
storeMasterKey(getInstallConfigFolder(), masterKey);
// Now you can perform the following steps:
//
// 1. (optionally) Save a copy of the master key offline
// 2. Generate DEK and store it in a keystore, encrypted with KEK
// 3. Destroy KEK in memory (call "masterKey.destroy()")
// 4. For each secret that you need to store derive an English passphrase from
// its alias and DEK, and store it in a keystore, encrypted with this passphrase
// 5. Destroy DEK in memory
}
}
- At system start-up, recover or re-generate the Master Key. The approaches are:
- Use our
KeyStore
to retrieve the Master Key from the known folder. - Open the known folder, sort the key material files in alphabetical order, then digest contents, names, and timestamps as before to re-generate the Master Key.
- Use our
- Use the Master Key to decrypt the Data Encryption Key (DEK). Wipe the Master Key from memory.
- When a key or passphrase is needed, re-generate it using the DEK and the proper alias.
import java.io.File;
import java.security.Key;
import java.security.KeyStore;
import java.security.Security;
import java.security.KeyStore.LoadStoreParameter;
import com.ibm.eht.sidekek.SecretFolderKeyStoreSpi;
import com.ibm.eht.sidekek.SideKEK;
public class UseMasterKey {
public static File getInstallConfigFolder() { // re-write for your app
return new File("./config");
}
private static Key loadMasterKey(File configFolder) throws Exception {
File keyStoreFolder = new File(configFolder, "keyStore");
KeyStore ks = KeyStore.getInstance(SideKEK.SECRET_FOLDER_KEYSTORE);
LoadStoreParameter param = new SecretFolderKeyStoreSpi.SecretFolderKeyStoreParameter(keyStoreFolder);
ks.load(param);
Key masterKey = ks.getKey("masterKey", null);
return masterKey;
}
public static void main(String args[]) throws Exception {
// Initialize the provider
Security.addProvider(new SideKEK());
// Load master Key
Key masterKey = loadMasterKey(getInstallConfigFolder());
// Now you can perform the following steps:
//
// 1. Load DEK and decrypt it with KEK
// 2. Destroy KEK in memory (call "masterKey.destroy()")
// 3. For each secret that you need to load, derive an English passphrase from
// its alias and DEK, and and load it from a keystore, decrypting with this passphrase
// 5. Destroy DEK in memory
}
}
- With the system offline, use the password protected backup of the Master Key to decrypt the Data Encryption Key (DEK).
- Use the same process used at initialization to create a new Master Key. Wipe all traces of the old Master Key material, and old backup, to prevent confusion.
- Create a new backup of the new Master Key. Again, this must be kept securely offline.
- Encrypt the existing DEK with the new Master Key and save the newly encrypted DEK. Delete the old encrypted DEK file to avoid confusion.
- The system will can now be started normally.