1

I often get this question:

I have sensitive data such as system credentials which my web application needs to secure. Some things I can store in a database, but others (like the credentials to access the database itself) need to be stored in a configuration file. How can I best secure those if I don't have access to an expensive hardware storage module or specialized crypto hardware?

I've been told it isn't possible -- that if an attacker gains access to the machine, they can find anything. What do I do?

1 Answer 1

1

It’s entirely true that if somebody has local access or root access to the system, they can certainly find the master key (also called the Key Encrypting Key or KEK). However, I find it is more likely that a remote attacker tries to obtain the key. There are methods that are resistant to most common remote attacks, and I recommend they are used in cases where you have sensitive information like database credentials.

The way I typically suggest this be handled is as follows:

At installation time, a random master key is generated. It is not hard-coded in the source code. It is stored on the local system either:

  1. in a randomly named file inside a randomly named folder, with access limited to the app id, or else
  2. it is stored inside the metadata of a set of randomly named files inside a randomly named folder (using a portion of the mtime timestamp). Again, access limited to the app id.

This master key is recovered when the system starts up, and is used to decrypt the data encrypting key (DEK). The master key is then cleared from memory. Its only purpose is to de/encrypt the DEK, which is stored in encrypted form in a config file. This DEK is used to encrypt any other secrets (like DB creds). Like the master key, the DEK is generated at installation time, encrypted with the KEK, and stored in a config file.

Advantages

  1. There is no hard-coded master key. Each installed app has a different master key, so if a rogue customer purchases the product, learning the master key does no good for attacking a different customer or a different installed app instance.

  2. If a remote attacker finds a means of exfiltrating file data, they will have difficulty given that the file name is random – typically an attacker will need to know the file name to exploit a path traversal issue, for example.

  3. If you store the master key in file metadata, then even if the file contents are exfiltrated, it’s useless. An attacker would require remote code execution to extract the key from file metadata. The files can be empty or (if you’re feeling like messing with attackers) can have random data – the file contents are not used, only the metadata.

Why two keys? What's the reason for both a master key and a data encryption key?

  1. If you've already written code, it's much simpler to add a master key to encrypt and protect your existing encryption key.
  2. If you grow wildly successful and choose to switch to a HSM, it's trivial to do so.
  3. If you're forced to periodically change keys, you can change only the master key, re-encrypt the data encryption key, and you're done. No need to find every secret and re-encrypt it.

How does the app itself locate the master key, given the file name and folder name are random?

Good question!

We assume there is one folder known to the app. Let’s say that this folder is named config/. Within config/, the code searches for a folder matching *.kek. Within that folder, search for all files matching *.kpt. Sort the filenames in alphabetical order. Retrieve the metadata from each file, and assemble the bytes into the master key. If your master key is 256 bits, then the random file names should also have 256 bits of entropy so that a brute force search for file names is at least as difficult as a brute force search of the keyspace.

config/
    …
    BcFkVQ_ne5JGYj4K4W4pzMTnOYB2sYmBUQ78nu5Elo0.kek/
       z2eYwmfXnJBcnC9M8fDovXV8bIG_jgDtK5656eUOplw.kpt
       UUFcJYUeokiM-AzPawHGMjlMTOZAI17yBZQd48zT3_M.kpt
       WD2CbKJce6iXfZuyq8Zqe3u1xQN2Op_QR1u4PCm72AA.kpt

I have a code library that does this for you. It’s in Java and is implemented using the Java KeyStore interface. You can also implement the same thing as a C library, if that’s more suitable. We contributed the library to OWASP as open source: https://github.com/OWASP/SideKEK

A presentation talking about the system is available: https://raw.githubusercontent.com/OWASP/SideKEK/master/media/KeysUnderDoormats-slides-GlobalAppSecDC-2019-09-13.pdf

A common question is, “How is this not security by obscurity?” Security by obscurity is an attempt to keep a secret by making the method difficult to understand. Here the method is documented and can be shouted from the rooftops. The thing that is secret is the actual folder and filenames, just like a secret key in a well-documented encryption scheme like AES. A good guiding rule to follow is Kerckhoffs's principle: "A cryptosystem should be secure even if everything about the system, except the key, is public knowledge."

A Few Notes

  1. Use a secure random number generator (secure PRNG) to generate keys.
  2. I recommend you salt the DEK with a static Alias string when encrypting a secret or when generating character passwords to secure secrets in a keystore. This way every secret is encrypted with a different key, and even identical secrets will have different encrypted results. The Alias does not need to be secret, only unique. This helps to make your code more understandable as well, if the Aliases are well-chosen.
  3. It's a good idea to generate an encrypted backup of the master key at installation time, and ensure it is stored OFFLINE (maybe mail it to the installer rather than keeping the backup file on the disk). Use password-based-encryption (PBE) like PBEWithHmacSHA256AndAES_128 to secure the backup. Just make sure the installer provides a long password and enters it identically twice. Programmatically test the backup before mailing it.
  4. If you provide the ability to make a backup after installation, do not make that feature accessible remotely. It would be incredibly dumb to let a remote user (abuser) request a backup of the master key!! Don't even do it as a part of test code. If written in Java, for example, I wouldn't even have backup code present in the deployed application's JAR files. Make it part of the install program or a separate console-only application. You went to all this trouble to secure the master key -- don't blow it now.
  5. Done properly, outside of the secrets you're protecting, you won't need any keys stored on disk except the encrypted DEK and the protected master key (KEK). Encrypted credentials are de/encrypted using keys derived from the DEK and Alias when needed, then immediately cleared from memory. Items stored in keystores are encrypted using character passwords derived from the DEK and Alias when needed, then immediately cleared from memory.

Example API doc for using a DEK

See link above for the OWASP project to protect a DEK using a master key (KEK). The below is some JavaDoc from a different bit of code that is for using a DEK to protect secrets like credentials in an application.

public class SecretGenerator
extends java.lang.Object

Static methods used to generate secret key material and passwords.

All methods require the caller supply a 'Data Encrypting Key' (DEK). The methods also require an 'alias' string. The alias is used so that a program can generate the same key material or the same password as often as is needed, by providing the same meaningful symbolic name and the same DEK. This prevents the need to save the key or password in a file between runs of the program.

Note that the alias parameter given to genPassword will be altered internally so that if the same alias is also given to genKeyMaterial, it will result in different data being generated. This prevents 'decoding' a password obtained using a given alias to obtain key material generated with the same alias value. This also means you can use the same alias for generating key material and a password with no concern about conflict.

Example Use: If you wanted to create then save a private key in a Java KeyStore (JKS) file, you could make these calls:

 // Generate a private key for encrypting check requests
 public static final char[]    CHECK_REQ_KEY = "Check Request Key".toCharArray();
 public static final int    CHECK_REQ_KEYLEN = 512; //bit size of key
 . . .
 // Obtain the data encryption key somehow (perhaps using a singleton)
 SecretKey dataKey = MasterKey.getInstance().getDataEncryptionKey();
 . . .
 // Create 512 bits of key material from the alias and data encryption key
 byte[] ckBytes = SecretGenerator.genKeyMaterial(
                        CHECK_REQ_KEY,  // alias 
                              dataKey,  // program's data encryption key
                     CHECK_REQ_KEYLEN); // number of bits we need
 // Create secret key from the generated bits
 SecretKey checkKey = new SecretKeySpec(ckBytes, 0, CHECK_REQ_KEYLEN / 8, "AES");
 // Wipe bits from memory, leaving only key in memory, for now
 SecretGenerator.wipe(ckBytes);
 
 // Create a password to secure the secret key in a JKS file
 char[] checkKeyPassword = SecretGenerator.genPassword(
                        CHECK_REQ_KEY,  // alias (internally 'PasswordAlias:Check Request Key')
                              dataKey,  // program's data encryption key
                     CHECK_REQ_KEYLEN,  // want a password with 512 bits of entropy
                                   -1); // let method determine password length
 . . .
 // make KeyStore calls to save key, and specify the 
 // char array checkKeyPassword to encrypt it in the key store
 . . .
 // wipe sensitive data from memory
 SecretGenerator.wipe(checkKeyPassword);
 checkKey.destroy();
 . . .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.