Developers often ask me how they can safely store secret information when building secure systems. A secret is any data known only to one or more valid computers, users, or applications. Examples include passwords, keys to decrypt other data, and so on. The short answer to their question is they can't; the long answer is the subject of this article.
Sometimes You Don't Need to Store the Secret
If you store a secret only to verify that another entity also knows the secret, then you probably don't need to store
the secret itself. Instead, you can store a "verifier," which often is the hash value of the secret. For example, if
an application needs to verify that a user knows a password, you can compare the hash value of the secret the user
entered with the hash value of the stored secret. In this case, the secret itself is not stored by the application and
thus presents less risk—if attackers break into the system, they cannot retrieve the secret itself.
To make things a little more difficult for an attacker, you can also "salt" the hash value. A salt is a random number
that is added to the hash value to stop pre-computed dictionary attacks, making an attempt to recover the original
secret extremely expensive. The salt is stored with the hash value. Choosing a hash function is an important decision.
Always use a cryptographically strong hash function, one that has been demonstrated to have no—or extremely
low—collision chances. In other words, creating two data that compute the same hash value should be infeasible. The
hash function de jour is SHA-1.
fallen somewhat out of favor as subtle vulnerabilities have been discovered in the algorithm.
Let's look at the steps required to store the hash value of the secret and validate the secret.
- Store the salted hash value.
Verify that the user knows the secret.
- Get the secret you wish to protect (for example, a user's password).
- Derive a random 128-bit number [use
CryptGenRandom()], this is the salt.
- Run a hash function on the secret [use
- Add the salt to your hash value [use CryptHashData()].
- Store the salt and the hash value.
- Get the secret from the user.
- Run a hash function on the secret [use CryptCreateHash() and CryptHashData()].
- Get the hash value and the salt from storage.
- Add the salt to hash value [use CryptHashData()].
- Compare the two salted hash values. If they are the same, then chances are the user knows the secret.
As you can see, you may be able to get away with not storing a secret, which is always preferable to storing one. But
sometimes you must
store the secret. So let's look at secure ways of doing so.
The Safest Way to Store Secrets
The most secure way to store and protect secrets is to get input from a user. This input can be used as the key to encrypt and decrypt the protected data. In other words, the secrets are protected with data held in a user's head; they are not persisted.
However, storing secrets this way can often become unusable for most users. The more items of information (number of passwords) you make them remember, the more likely they are to use the same password over and over, which reduces the system's security and usability, and increases complexity.
Now let's turn our attention to the more complex issues of the error-prone method: storing secrets without prompting for user-defined keys.