What is it and why should I care?
Encryption (specifically talking symmetric encryption here) is a critical component of many applications, and the storage of the encryption key can be tricky to get right. Encryption falls under that area of secure programming that you don’t come into contact with casually, hence you might not be practiced in secure key storage. You might run across SQLi since it’s common to use a database, or XSS because you’re programming for the web, but you usually run into encryption explicitly. There are a couple of extremely common usages of encryption in normal programming: encrypting application data (credit card, SSN, etc.) or encrypting system account credentials (user/pw pair for connecting to a DB or web service call). Both of these cases require the keys to be accessible to the application at runtime in order to function properly, so we have to figure out how to solve that problem correctly.
In addition to being less common or prolific than other security issues (SQLi, XSS), there is generally less information available about how to accomplish the task properly. To be fair, that’s not necessarily because of its less common nature, but more because there is no one right answer for how you should protect your encryption key – there are various options and your specific circumstances will at least partially dictate the solution. It’s more nuanced than “use prepared statements and no dynamic sql!”. While there’s no single right answer, there are some common good and bad approaches which I’ll attempt to address.
What should I do about it?
Before looking at solutions, I want to briefly address 2 base assumptions I’ve made which are worth considering before digging in deeper:
1. Store as little sensitive data as needed
You should only store the sensitive data that you must. If you can throw it away, then by all means throw it away. There are lots of factors that go into this decision, but from a security perspective, you can’t lose data you don’t have.
2. Use well-vetted implementations of strong encryption algorithms in the proper configuration
There are 3 subpoints here:
– Strong encryption algorithms: Check NIST for the most up-to-date list of acceptable encryption algorithms. A simple example is that you should no longer be using DES for encryption, but AES is currently ok (as of late 2012)
– Proper configuration: Again, check NIST. You should be using the appropriate mode and padding for your encryption algorithm in order for it to be more resistant to cryptanalysis. A simple example here is that you should not be using ECB as a mode – it is not safe.
– Well-vetted implementations: You should be using algorithms and configurations that are approved, but the implementation should be well-known and reasonably vetted. The level of rigor for the vetting will differ depending upon your environment, but in general, java programmers are usually safe with either the built-in implementations from Oracle, or using BouncyCastle, a popular crypto library.
Now that we’ve considered our assumptions, let’s look at a few things you should not do when considering how to store your encryption key:
– Don’t store your key with the data it is protecting.
You shouldn’t store your key together with your data, because if the attacker gets one, they likely have the other. That means the attacker has nothing but time to get your data. Two common ways this shows itself is either with the key stored in the database with the data, or the key stored in a flat file with the username and password it’s protecting. This is a bad practice.
– Don’t store your key on the same host as the data it is protecting.
This is very similar to the previous point, but also takes into account the key in one file and the username/password in another file. If they’re both on the same server, it’s more likely the attacker will be able to get to both. Also, if the DB is local to the appserver as is the key file, then the likelihood increases that both could be grabbed at the sametime for an offline attack.
– Don’t rely on security by obscurity.
Encryption key storage is an area where I see obscurity used more often than in other places in applications. People try to do clever things like break the key up across several files or obfuscate the key in some way, and then reverse it out in the application. I’m not saying these tactics couldn’t increase the security marginally – they might depending on the attack vector. However, they also usually complicate processes like key rotation and data re-keying. My vote is to stick to simpler solid practices. If you do go this route, make sure you have a complete solution that takes into account your full key lifecycle.
We’ve looked at a few bad techniques, now let’s consider some good practices.
– Separate key and protected data
I’ve already covered this above with the bad practices, but making sure the encryption key and the data it is protecting are separate is certainly a good practice. Make sure it’s separate across tiers as well.
– Lock down your permissions
Make sure that your account credentials are locked down appropriately, including DB, filesystem, web service calls, etc. Ensure that you are using strong authentication and access control as well as least privilege. For instance, there’s usually no reason for the encryption key to be writable by an application – read-only makes sense in this case.
– Consider various storage options.
There are a multitude of ways you could store your encryption key depending on your needs. A few might be a keystore (java keystore), an HSM (hardware security module), flat file, DB, etc. You may also allow for the key to be manually entered by operations personnel at application startup time. This puts key storage out of band generally, but does mean that there are additional runtime requirements you must live with, but that may be the right option for you. Again, I want to point out that not all of these will work for everyone, but it’s good to know they are available so you can see which solution will work in your environment.
– Plan for key rotation and re-keying data
Whether it’s driven by internal or external requirements, a data breach, or just good practice, you should be doing key rotation and data re-keying. A key should have a general lifecycle that takes it from generation to destruction. By following this process, you shorten the window for which the key is useful, and you have a recourse if you are breached. The hope during a breach is that either the data or the key is captured but not both. When planning for this step, be sure to consider issues like disaster recovery and backed up data that may need to be retrieved. Those processes should play into the key lifecycle as well.
– Know your policies, standards and regulations
Depending on your industry or even just your organization, you will have both internal and external requirements that you must account for. For instance, if you process and store credit cards, you have to live with PCI/DSS. There are certain requirements that these standards levy upon you, and those may impact your key management process. Be aware of what they are and make sure you are abiding by those when you build out your plan.
In conclusion, encryption key storage is not a simple, clear-cut security issue. It is nuanced, and the appropriate solution depends on your policy and regulatory environment. However, there are some well known good and bad practices you should consider before coming up with a solution. With attention to these details, you can come up with a solution that fits your needs and provides solid security as well.