Key derivation in .NET using HKDF
Tags: dotnet, csharp, security, programming
Published: 2024-03-04 12:30:00

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](https://csrc.nist.gov/pubs/sp/800/56/c/r2/final),
- [[RFC 5869] HMAC-based Extract-and-Expand Key Derivation Function (HKDF)](https://datatracker.ietf.org/doc/html/rfc5869).

The prerequisite is to already have an cryptographically secure random key, for example, by using the [RandomNumberGenerator](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.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](https://datatracker.ietf.org/doc/html/rfc5869#section-3.3). 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.

![HKDF Expand function diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdbgqx8lhfuga2r0lng1.png)

Since .NET 5, the HKDF function is available via the [HKDF](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.hkdf) class. For earlier versions, the [Bouncy Castle for .NET](https://www.bouncycastle.org/csharp/) 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:

```csharp
// 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](https://www.nuget.org/packages/BouncyCastle.Cryptography) package has been installed:

```csharp
// 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.