2.4. Key Derivation Functions

Key derivation functions (KDFs) can be used to deterministically generate multiple keys from a single key (via crypto.HashKDF) to derive a single key from a password (via crypto.PasswordKDF). These functions can help (1) simplify your key management schemes and (2) avoid dangerous design patterns, such as reusing the same key for multiple purposes.

Important

One key, one purpose. If we use a key for symmetric encryption or HMAC, we should not use the same key to derive other keys via crypto.HashKDF. Similarly, we should not use the same key for both encryption and HMAC.

2.4.1. Password-Based Key Derivation

crypto.PasswordKDF(password: str, salt: bytes, key_length: int) bytes

Takes a password and a salt and returns a new key. The size of the output equals key_length (in bytes).

This function is built on top of PBKDF2, which is a deliberately slow and memory-intensive KDF as to make brute-force attacks infeasible for the adversary. As such, this function is a cryptographically secure way to derive a key from a password as long as the password has at least a medium level of entropy (approximately 40 bits). (However, if password has low entropy, brute-force attacks are still feasible, and attempting to derive a cryptographic key from a low-entropy password will produce an insecure key.)

Users may happen to choose the same password for their account, which means that a salt-less password-based key derivation function will produce the same key for each user. Thus, the salt should be different for each user, which makes it more infeasible for adversaries to launch a brute-force attack against keys derived from passwords.

Parameters:
  • password (str) – A password with at least a medium level of entropy

  • salt (bytes) – A salt value

  • key_length (int) – Desired length of the key to derive

Returns:

A new key

Return type:

bytes

Warning

Using the same constant salt for every user may enable the adversary to use a rainbow table to break keys derived from this function; similarly, using a salt derived deterministically from a user’s username enables precomputation of rainbow tables against specific high-value, targeted users.

2.4.2. Hash-Based Key Derivation

crypto.HashKDF(key: bytes, purpose: str) bytes

Given a key and a purpose, derives a new key from the input parent key key that has the same length as the input key.

This function is deterministic, so it will always return the same key given the same key and purpose. As such, if the input key is insecure because it has insufficient entropy, then keys derived from key will be insecure for the same reason.

Parameters:
  • key (bytes) – The “parent” key to derive from

  • purpose (str) – An arbitrary, human-readable string that explains the purpose of the newly derived “child” key

Returns:

A new key that is the same length as the input key

Return type:

bytes

Example usage:

base_key = crypto.SecureRandom(16)

derived_key_1 = crypto.HashKDF(base_key, "encryption")
derived_key_2 = crypto.HashKDF(base_key, "mac")

# Derived keys are the same length as the input key:
assert(len(base_key) == len(derived_key_1))
assert(len(base_key) == len(derived_key_2))

derived_key_3 = crypto.HashKDF(base_key, "encryption")

# Using the same base key and purpose results in the same derived key:
assert(derived_key_1 == derived_key_3)

Note

purpose is an arbitrary string. Changing purpose to different values allows you to generate multiple keys from a single key, which can help simplify your key management schemes.

Danger

You should not use HashKDF to create a key from a low-entropy input like a password, as this will create another low-entropy key. Instead, you can derive an initial key from a password using crypto.PasswordKDF and then use HashKDF to derive additional keys from this initial key.