Books / Crypto 101 / Chapter 9
Key derivation functions
In this chapter
A key derivation function is a function that derives one or more secret values (the keys) from one secret value.
Many key derivation functions can also take a (usually optional)
salt
parameter. This parameter causes
the key derivation function to not always return the same output keys
for the same input secret. As with other cryptosystems,
salt
s are fundamentally different from
the secret input: salt
s generally do
not have to be secret, and can be re-used.
Key derivation functions can be useful, for example, when a cryptographic protocol starts with a single secret value, such as a shared password or a secret derived using Diffie-Hellman key exchange, but requires multiple secret values to operate, such as encryption and MAC keys. Another use case of key derivation functions is in cryptographically secure random number generators, which we’ll see in more detail in a following chapter, where they are used to extract randomness with high entropy density from many sources that each have low entropy density.
There are two main categories of key derivation functions, depending on the entropy content of the secret value, which determines how many different possible values the secret value can take.
If the secret value is a user-supplied password, for example, it typically contains very little entropy. There are very few values the password will take. As we’ve already established in `a previous section on password storage, that means it is necessary that the key derivation function is hard to compute. That means it requires a non-trivial amount of computing resources, such as CPU cycles or memory. If the key derivation function were easy to compute, an attacker could simply enumerate all possible values of the shared secret, since there are few possibilities, and then compute the key derivation function for all of them. As we’ve seen in that previous section on password storage, this is how most modern attacks on password stores work. Using an appropriate key derivation function would prevent these attacks. In this chapter, we’ll see scrypt, as well as other key derivation functions in this category.
On the other hand, the secret value could also have a high entropy content. For example, it could be a shared secret derived from a Diffie-Hellman key agreement protocol, or an API key consisting of cryptographically random bytes (we’ll discuss cryptographically secure random number generation in the next chapter). In that case, it isn’t necessary to have a key derivation function that’s hard to compute: even if the key derivation function is trivial to compute, there are too many possible values the secret can take, so an attacker would not be able to enumerate them all. We’ll see the best-of-breed of this kind of key derivation function, HKDF, in this chapter.
Password strength
TODO: NIST Special Publication 800-63
PBKDF2
bcrypt
scrypt
HKDF
The HKDF, defined in RFC 5869 and explained in detail in a related paper, is a key derivation function designed for high entropy inputs, such as shared secrets from a Diffie-Hellman key exchange. It is specifically not designed to be secure for low-entropy inputs such as passwords.
HKDF exists to give people an appropriate, off-the-shelf key derivation
function. Previously, key derivation was often something that was done
ad hoc for a particular standard. Usually these ad hoc solutions did not
have the extra provisions HKDF does, such as salt
s or the optional info parameter (which we’ll discuss later
in this section); and that’s only in the best case scenario where the
KDF wasn’t fundamentally broken to begin with.
HKDF is based on HMAC. Like HMAC, it is a generic construction that uses hash functions, and can be built using any cryptographically secure hash function you want.
A closer look at HKDF
HKDF consists of two phases. In the first phase, called the extraction phase, a fixed-length key is extracted from the input entropy. In the second phase, called the expansion phase, that key is used to produce a number of pseudorandom keys.
The extraction phase
The extraction phase is responsible for extracting a small amount of data with a high entropy content from a potentially large amount of data with a smaller entropy density.
The extraction phase just uses HMAC with a salt
:
def extract(salt, data):
return hmac(salt, data)
The salt
value is optional. If the
salt
is not specified, a string of
zeroes equal to the length of the hash function’s output is used. While
the salt
is technically optional, the
designers stress its importance, because it makes the independent uses
of the key derivation function (for example, in different applications,
or with different users) produce independent results. Even a fairly
low-entropy salt
can already contribute
significantly to the security of the key derivation function.
The extraction phase explains why HKDF is not suitable for deriving keys from passwords. While the extraction phase is very good at concentrating entropy, it is not capable of amplifying entropy. It is designed for compacting a small amount of entropy spread out over a large amount of data into the same amount of entropy in a small amount of data, but is not designed for creating a set of keys that are difficult to compute in the face of a small amount of available entropy. There are also no provisions for making this phase computationally intensive.
In some cases, it is possible to skip the extraction phase, if the shared secret already has all the right properties, for example, if it is a pseudorandom string of sufficient length, and with sufficient entropy. However, sometimes this should not be done at all, for example when dealing with a Diffie-Hellman shared secret. The RFC goes into slightly more detail on the topic of whether or not to skip this step; but it is generally inadvisable.
The expansion phase
In the expansion phase, the random data extracted from the inputs in the extraction phase is expanded into as much data as is required.
The expansion step is also quite simple: chunks of data are produced
using HMAC, this time with the extracted secret, not with the public
salt
, until enough bytes are produced.
The data being HMACed is the previous output (starting with an empty
string), an “info” parameter (by default also the empty string), and a
counter byte that counts which block is currently being produced.
def expand(key, info=""):
"""Expands the key, with optional info."""
output = ""
for byte in map(chr, range(256)):
output = hmac(key, output + info + byte)
yield output
def get_output(desired_length, key, info=""):
"""Collects output from the expansion step until enough
has been collected; then returns that output."""
outputs, current_length = [], 0
for output in expand(key, info):
outputs.append(output)
current_length += len(output)
if current_length >= desired_length:
break
else:
# This block is executed when the for loop *isn't*
# terminated by the ``break`` statement, which
# happens when we run out of ``expand`` outputs
# before reaching the desired length.
raise RuntimeError("Desired length too long")
return "".join(outputs)[:desired_length]
Like the salt
in the extraction phase,
the “info” parameter is entirely optional, but can actually greatly
increase the security of the application. The “info” parameter is
intended to contain some application-specific context in which the key
derivation function is being used. Like the salt
, it will cause the key derivation function to produce
different values in different contexts, further increasing its security.
For example, the info parameter may contain information about the user
being dealt with, the part of the protocol the key derivation function
is being executed for or the like.