Key derivation is a process that allows you to create one or more keys from a single primary key. Rather than storing multiple individual keys that serve different purposes, it's possible to derive them as needed from a primary key. For example, to use the AES algorithm for encrypting data with HMAC authentication for a specific user, you can derive an AES key and an HMAC key from a single primary key.
In this article, we will look at using the HKDF key derivation function, as described in the following documents:
- [NIST SP 800-56C Rev. 2] Recommendation for Key-Derivation Methods in Key-Establishment Schemes,
- [RFC 5869] HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
The prerequisite is to already have an cryptographically secure random key, for example, by using the RandomNumberGenerator class.
The HKDF function consists of two stages: extract and expand. The extract stage is only required if the initial key material does not have sufficient entropy. As we will use a primary key in the form of cryptographically secure random bytes generated by RandomNumberGenerator
, this stage can be omitted.
The expansion is the phase that interests us. We provide the primary key and some contextual information (known as the info
parameter) to derive a key. This information can include a user identifier, a tenant identifier, etc. It represents the purpose of the derived key.
Since .NET 5, the HKDF function is available via the HKDF class. For earlier versions, the Bouncy Castle for .NET library offers a compatible implementation of HKDF.
Using HKDF in .NET 5 and later
Here's how to use the HKDF
class to derive a key for AES 256 CBC encryption with HMAC SHA256 authentication:
// Generating a cryptographically secure random key, but you should use a key that is persisted and securely protected.
var primaryKey = RandomNumberGenerator.GetBytes(32);
// We're going to assume we want to encrypt some data for a specific user.
var userId = "user123";
// Deriving two keys for this user, one for AES and one for HMAC.
var aesKey = HKDF.Expand(HashAlgorithmName.SHA256, primaryKey, 32, info: Encoding.UTF8.GetBytes(userId + "AES256CBC"));
var hmacKey = HKDF.Expand(HashAlgorithmName.SHA256, primaryKey, 32, info: Encoding.UTF8.GetBytes(userId + "HMACSHA256"));
Using HKDF in .NET Framework and .NET Core pre-5.0
Here's the equivalent for .NET Framework and .NET Core before version 5.0, assuming the BouncyCastle.Cryptography package has been installed:
// Generating a cryptographically secure random key, but you should use a key that is persisted and securely protected.
var primaryKey = RandomNumberGenerator.GetBytes(32);
// We're going to assume we want to encrypt some data for a specific user.
var userId = "user123";
// Deriving two keys for this user, one for AES and one for HMAC.
var aesKeyParams = HkdfParameters.SkipExtractParameters(primaryKey, Encoding.UTF8.GetBytes(userId + "AES256CBC"));
var aesKeyGenerator = new HkdfBytesGenerator(new Sha256Digest());
var aesKey = new byte[32];
aesKeyGenerator.Init(aesKeyParams);
aesKeyGenerator.GenerateBytes(aesKey, 0, aesKey.Length);
var hmacKeyParams = HkdfParameters.SkipExtractParameters(primaryKey, Encoding.UTF8.GetBytes(userId + "HMACSHA256"));
var hmacKeyGenerator = new HkdfBytesGenerator(new Sha256Digest());
var hmacKey = new byte[32];
hmacKeyGenerator.Init(hmacKeyParams);
hmacKeyGenerator.GenerateBytes(hmacKey, 0, hmacKey.Length);
Additional notes
Both .NET's HDKF and Bouncy Castle's HKDF implementation provide more efficient APIs that leverage
Span<T>
instead of byte arrays. Consider using these APIs for better performance and memory usage.In my examples, I used a 32-byte primary key and derived 32-byte keys for AES and HMAC because I chose the AES 256 CBC and HMAC SHA256 algorithms. You should adjust the key sizes according to the algorithms you use.
Top comments (0)