120 lines
6.3 KiB
C#
120 lines
6.3 KiB
C#
|
//------------------------------------------------------------------------------
|
|||
|
// <copyright file="SP800_108.cs" company="Microsoft">
|
|||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|||
|
// </copyright>
|
|||
|
//------------------------------------------------------------------------------
|
|||
|
|
|||
|
namespace System.Web.Security.Cryptography {
|
|||
|
using System;
|
|||
|
using System.Security.Cryptography;
|
|||
|
|
|||
|
/******************************************************************
|
|||
|
* !! WARNING !! *
|
|||
|
* This class contains cryptographic code. If you make changes to *
|
|||
|
* this class, please have it reviewed by the appropriate people. *
|
|||
|
******************************************************************/
|
|||
|
|
|||
|
// Implements the NIST SP800-108 key derivation routine in counter mode with an HMAC PRF (HMACSHA512).
|
|||
|
// See: http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf
|
|||
|
//
|
|||
|
// The algorithm is defined as follows:
|
|||
|
//
|
|||
|
// INPUTS:
|
|||
|
// PRF = The pseudo-random function used for key derivation; in our case, an HMAC.
|
|||
|
// KI = The key derivation key (master key) from which keys will be derived.
|
|||
|
// Label = The purpose of the derived key.
|
|||
|
// Context = Information related to the derived key, such as consuming party identities or a nonce.
|
|||
|
// L = The desired length (in bits) of the derived key.
|
|||
|
//
|
|||
|
// ALGORITHM:
|
|||
|
// Let n = ceil(L / HMAC-output-size)
|
|||
|
// For i = 1 to n,
|
|||
|
// K_i = PRF(KI, [i]_2 || Label || 0x00 || Context || [L]_2)
|
|||
|
// where [x]_2 = the big-endian representation of 'x'
|
|||
|
//
|
|||
|
// OUTPUT:
|
|||
|
// Result := K_1 || K_2 || ... || K_n, truncated to be L bits in length
|
|||
|
|
|||
|
internal static class SP800_108 {
|
|||
|
|
|||
|
// Implements the KeyDerivationFunction delegate signature; public entry point to the API.
|
|||
|
public static CryptographicKey DeriveKey(CryptographicKey keyDerivationKey, Purpose purpose) {
|
|||
|
// After consultation with the crypto board, we have decided to use HMACSHA512 as the PRF
|
|||
|
// to our KDF. The reason for this is that our PRF is an HMAC, so the total entropy of the
|
|||
|
// PRF is given by MIN(key derivation key length, HMAC block size). It is conceivable that
|
|||
|
// a developer might specify a key greater than 256 bits in length, at which point using
|
|||
|
// a shorter PRF like HMACSHA256 starts discarding entropy. But from the crypto team's
|
|||
|
// perspective it is unreasonable for a developer to supply a key greater than 512 bits,
|
|||
|
// so there's no real harm in us limiting our PRF entropy to 512 bits (HMACSHA512).
|
|||
|
//
|
|||
|
// On 64-bit platforms, HMACSHA512 matches or outperforms HMACSHA256 in our perf testing.
|
|||
|
// On 32-bit platforms, HMACSHA512 is around 1/3 the speed of HMACSHA256. In both cases, we
|
|||
|
// try to cache the derived CryptographicKey wherever we can, so this shouldn't be a
|
|||
|
// bottleneck regardless.
|
|||
|
|
|||
|
using (HMACSHA512 hmac = CryptoAlgorithms.CreateHMACSHA512(keyDerivationKey.GetKeyMaterial())) {
|
|||
|
byte[] label, context;
|
|||
|
purpose.GetKeyDerivationParameters(out label, out context);
|
|||
|
|
|||
|
byte[] derivedKey = DeriveKeyImpl(hmac, label, context, keyDerivationKey.KeyLength);
|
|||
|
return new CryptographicKey(derivedKey);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// NOTE: This method also exists in Win8 (as BCryptKeyDerivation) and QTD (as DeriveKeySP800_108).
|
|||
|
// However, the QTD implementation is currently incorrect, so we can't depend on it here. The below
|
|||
|
// is a correct implementation. When we take a Win8 dependency, we can call into BCryptKeyDerivation.
|
|||
|
private static byte[] DeriveKeyImpl(HMAC hmac, byte[] label, byte[] context, int keyLengthInBits) {
|
|||
|
// This entire method is checked because according to SP800-108 it is an error
|
|||
|
// for any single operation to result in overflow.
|
|||
|
checked {
|
|||
|
|
|||
|
// Make a buffer which is ____ || label || 0x00 || context || [l]_2.
|
|||
|
// We can reuse this buffer during each round.
|
|||
|
|
|||
|
int labelLength = (label != null) ? label.Length : 0;
|
|||
|
int contextLength = (context != null) ? context.Length : 0;
|
|||
|
byte[] buffer = new byte[4 /* [i]_2 */ + labelLength /* label */ + 1 /* 0x00 */ + contextLength /* context */ + 4 /* [L]_2 */];
|
|||
|
|
|||
|
if (labelLength != 0) {
|
|||
|
Buffer.BlockCopy(label, 0, buffer, 4, labelLength); // the 4 accounts for the [i]_2 length
|
|||
|
}
|
|||
|
if (contextLength != 0) {
|
|||
|
Buffer.BlockCopy(context, 0, buffer, 5 + labelLength, contextLength); // the '5 +' accounts for the [i]_2 length, the label, and the 0x00 byte
|
|||
|
}
|
|||
|
WriteUInt32ToByteArrayBigEndian((uint)keyLengthInBits, buffer, 5 + labelLength + contextLength); // the '5 +' accounts for the [i]_2 length, the label, the 0x00 byte, and the context
|
|||
|
|
|||
|
// Initialization
|
|||
|
|
|||
|
int numBytesWritten = 0;
|
|||
|
int numBytesRemaining = keyLengthInBits / 8;
|
|||
|
byte[] output = new byte[numBytesRemaining];
|
|||
|
|
|||
|
// Calculate each K_i value and copy the leftmost bits to the output buffer as appropriate.
|
|||
|
|
|||
|
for (uint i = 1; numBytesRemaining > 0; i++) {
|
|||
|
WriteUInt32ToByteArrayBigEndian(i, buffer, 0); // set the first 32 bits of the buffer to be the current iteration value
|
|||
|
byte[] K_i = hmac.ComputeHash(buffer);
|
|||
|
|
|||
|
// copy the leftmost bits of K_i into the output buffer
|
|||
|
int numBytesToCopy = Math.Min(numBytesRemaining, K_i.Length);
|
|||
|
Buffer.BlockCopy(K_i, 0, output, numBytesWritten, numBytesToCopy);
|
|||
|
numBytesWritten += numBytesToCopy;
|
|||
|
numBytesRemaining -= numBytesToCopy;
|
|||
|
}
|
|||
|
|
|||
|
// finished
|
|||
|
return output;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void WriteUInt32ToByteArrayBigEndian(uint value, byte[] buffer, int offset) {
|
|||
|
buffer[offset + 0] = (byte)(value >> 24);
|
|||
|
buffer[offset + 1] = (byte)(value >> 16);
|
|||
|
buffer[offset + 2] = (byte)(value >> 8);
|
|||
|
buffer[offset + 3] = (byte)(value);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|