Created
June 12, 2019 07:09
-
-
Save badcommandorfilename/a6dee79ff2d46be29aeb5a9890cb8d90 to your computer and use it in GitHub Desktop.
Standalone (mostly) SP800_108_CTR_HMACSHA512 Key Derivation Function for ASPNetCore cookie sharing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///This is a Frankenstein class that can extract the AES key from a KDK as described in: | |
/// https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/subkeyderivation?view=aspnetcore-2.2 | |
///With a bit of luck, this should let an ASP.NET app decrypt cookies generated by an ASPNETCore app | |
///Still consult https://docs.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-2.2 to share cookies | |
///Credit for most of the code is from various parts of https://github.com/aspnet/AspNetCore/tree/master/src/DataProtection/DataProtection/src | |
public unsafe class CookieDataProtector : IDataProtector | |
{ | |
readonly string _base64MasterKey; | |
public string ApplicationName { get; set; } | |
public string[] Purposes { get; set; } | |
public CookieDataProtector(string base64MasterKey, string applicationName, params string[] purposes) | |
{ | |
Purposes = purposes; | |
ApplicationName = applicationName; | |
_base64MasterKey = base64MasterKey; | |
} | |
public IDataProtector CreateProtector(string purpose) | |
{ | |
return this; | |
} | |
public byte[] Protect(byte[] plaintext) | |
{ | |
var serviceProtector = CreateProtector(""); | |
return serviceProtector.Protect(plaintext); | |
} | |
public byte[] Unprotect(byte[] protectedData) | |
{ | |
//https://stackoverflow.com/questions/42842511/how-to-manually-decrypt-an-asp-net-core-authentication-cookie/42857830 | |
//var elements = keyChain.GetAllElements(); //If you have a handle to a KeyChain | |
var kdk = Convert.FromBase64String(_base64MasterKey); | |
// Parse the payload version number and key id. | |
uint magicHeaderFromPayload; | |
Guid keyIdFromPayload; | |
fixed (byte* pbInput = protectedData) | |
{ | |
magicHeaderFromPayload = ReadBigEndian32BitInteger(pbInput); | |
keyIdFromPayload = Read32bitAlignedGuid(&pbInput[sizeof(uint)]); | |
} | |
///Important! | |
///The "Purpose" header is actually [Application Name] + [Purpose 1] ... [Purpose N] | |
///All these strings MUST MATCH the cookie generator EXACTLY | |
var _aadTemplate = new AdditionalAuthenticatedDataTemplate( | |
new [] { ApplicationName } //Application Name as per services.AddDataProtection().SetApplicationName("...") | |
.Union(Purposes) //Other purposes e.g. .CreateProtector(... | |
.ToArray()); | |
ArraySegment<byte> ciphertext = new ArraySegment<byte>(protectedData, sizeof(uint) + sizeof(Guid), protectedData.Length - (sizeof(uint) + sizeof(Guid))); // chop off magic header + encryptor id | |
ArraySegment<byte> additionalAuthenticatedData = new ArraySegment<byte>(_aadTemplate.GetAadForKey(keyIdFromPayload, isProtecting: false)); | |
///Refer to https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/subkeyderivation?view=aspnetcore-2.2 | |
var m = new ManagedAuthenticatedEncryptor( | |
kdk, | |
Aes.Create, | |
256 / 8, //32 byte AES key | |
() => new HMACSHA256()); //SHA 256 validation algorithm | |
// Perform the decryption operation. | |
var plainBytes = m.Decrypt(ciphertext, additionalAuthenticatedData); | |
//Get the decrypted cookie as plain text | |
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); | |
string plainText = specialUtf8Encoding.GetString(plainBytes); //Not used, but sanity check here | |
return plainBytes; | |
} | |
// Helper function to write a GUID to a 32-bit alignment; useful on ARM where unaligned reads | |
// can result in weird behaviors at runtime. | |
private static void Write32bitAlignedGuid(void* ptr, Guid value) | |
{ | |
Debug.Assert((long)ptr % 4 == 0); | |
((int*)ptr)[0] = ((int*)&value)[0]; | |
((int*)ptr)[1] = ((int*)&value)[1]; | |
((int*)ptr)[2] = ((int*)&value)[2]; | |
((int*)ptr)[3] = ((int*)&value)[3]; | |
} | |
// Helper function to read a GUID from a 32-bit alignment; useful on architectures where unaligned reads | |
// can result in weird behaviors at runtime. | |
private static Guid Read32bitAlignedGuid(void* ptr) | |
{ | |
Debug.Assert((long)ptr % 4 == 0); | |
Guid retVal; | |
((int*)&retVal)[0] = ((int*)ptr)[0]; | |
((int*)&retVal)[1] = ((int*)ptr)[1]; | |
((int*)&retVal)[2] = ((int*)ptr)[2]; | |
((int*)&retVal)[3] = ((int*)ptr)[3]; | |
return retVal; | |
} | |
private static uint ReadBigEndian32BitInteger(byte* ptr) | |
{ | |
return ((uint)ptr[0] << 24) | |
| ((uint)ptr[1] << 16) | |
| ((uint)ptr[2] << 8) | |
| ((uint)ptr[3]); | |
} | |
private struct AdditionalAuthenticatedDataTemplate | |
{ | |
//ref https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/subkeyderivation?view=aspnetcore-2.2 | |
private const uint MAGIC_HEADER_V0 = 0x09F0C9F0; | |
private byte[] _aadTemplate; | |
public AdditionalAuthenticatedDataTemplate(params string[] purposes) | |
{ | |
const int MEMORYSTREAM_DEFAULT_CAPACITY = 0x100; // matches MemoryStream.EnsureCapacity | |
var ms = new MemoryStream(MEMORYSTREAM_DEFAULT_CAPACITY); | |
// additionalAuthenticatedData := { magicHeader (32-bit) || keyId || purposeCount (32-bit) || (purpose)* } | |
// purpose := { utf8ByteCount (7-bit encoded) || utf8Text } | |
using (var writer = new PurposeBinaryWriter(ms)) | |
{ | |
writer.WriteBigEndian(MAGIC_HEADER_V0); | |
Debug.Assert(ms.Position == sizeof(uint)); | |
var posPurposeCount = writer.Seek(sizeof(Guid), SeekOrigin.Current); // skip over where the key id will be stored; we'll fill it in later | |
writer.Seek(sizeof(uint), SeekOrigin.Current); // skip over where the purposeCount will be stored; we'll fill it in later | |
uint purposeCount = 0; | |
foreach (string purpose in purposes) | |
{ | |
Debug.Assert(purpose != null); | |
writer.Write(purpose); // prepends length as a 7-bit encoded integer | |
purposeCount++; | |
} | |
// Once we have written all the purposes, go back and fill in 'purposeCount' | |
writer.Seek(checked((int)posPurposeCount), SeekOrigin.Begin); | |
writer.WriteBigEndian(purposeCount); | |
} | |
_aadTemplate = ms.ToArray(); | |
} | |
public byte[] GetAadForKey(Guid keyId, bool isProtecting) | |
{ | |
// Multiple threads might be trying to read and write the _aadTemplate field | |
// simultaneously. We need to make sure all accesses to it are thread-safe. | |
var existingTemplate = Volatile.Read(ref _aadTemplate); | |
Debug.Assert(existingTemplate.Length >= sizeof(uint) /* MAGIC_HEADER */ + sizeof(Guid) /* keyId */); | |
// If the template is already initialized to this key id, return it. | |
// The caller will not mutate it. | |
fixed (byte* pExistingTemplate = existingTemplate) | |
{ | |
if (Read32bitAlignedGuid(&pExistingTemplate[sizeof(uint)]) == keyId) | |
{ | |
return existingTemplate; | |
} | |
} | |
// Clone since we're about to make modifications. | |
// If this is an encryption operation, we only ever encrypt to the default key, | |
// so we should replace the existing template. This could occur after the protector | |
// has already been created, such as when the underlying key ring has been modified. | |
byte[] newTemplate = (byte[])existingTemplate.Clone(); | |
fixed (byte* pNewTemplate = newTemplate) | |
{ | |
Write32bitAlignedGuid(&pNewTemplate[sizeof(uint)], keyId); | |
if (isProtecting) | |
{ | |
Volatile.Write(ref _aadTemplate, newTemplate); | |
} | |
return newTemplate; | |
} | |
} | |
private sealed class PurposeBinaryWriter : BinaryWriter | |
{ | |
public static readonly UTF8Encoding SecureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); | |
public PurposeBinaryWriter(MemoryStream stream) : base(stream, SecureUtf8Encoding, leaveOpen: true) { } | |
// Writes a big-endian 32-bit integer to the underlying stream. | |
public void WriteBigEndian(uint value) | |
{ | |
var outStream = BaseStream; // property accessor also performs a flush | |
outStream.WriteByte((byte)(value >> 24)); | |
outStream.WriteByte((byte)(value >> 16)); | |
outStream.WriteByte((byte)(value >> 8)); | |
outStream.WriteByte((byte)(value)); | |
} | |
} | |
} | |
// An encryptor which does Encrypt(CBC) + HMAC using SymmetricAlgorithm and HashAlgorithm. | |
// The payloads produced by this encryptor should be compatible with the payloads | |
// produced by the CNG-based Encrypt(CBC) + HMAC authenticated encryptor. | |
internal unsafe sealed class ManagedAuthenticatedEncryptor | |
{ | |
// Even when IVs are chosen randomly, CBC is susceptible to IV collisions within a single | |
// key. For a 64-bit block cipher (like 3DES), we'd expect a collision after 2^32 block | |
// encryption operations, which a high-traffic web server might perform in mere hours. | |
// AES and other 128-bit block ciphers are less susceptible to this due to the larger IV | |
// space, but unfortunately some organizations require older 64-bit block ciphers. To address | |
// the collision issue, we'll feed 128 bits of entropy to the KDF when performing subkey | |
// generation. This creates >= 192 bits total entropy for each operation, so we shouldn't | |
// expect a collision until >= 2^96 operations. Even 2^80 operations still maintains a <= 2^-32 | |
// probability of collision, and this is acceptable for the expected KDK lifetime. | |
private const int KEY_MODIFIER_SIZE_IN_BYTES = 128 / 8; | |
private static readonly Func<byte[], HashAlgorithm> _kdkPrfFactory = key => new HMACSHA512(key); // currently hardcoded to SHA512 | |
private readonly byte[] _contextHeader; | |
private readonly byte[] _keyDerivationKey; | |
private readonly Func<SymmetricAlgorithm> _symmetricAlgorithmFactory; | |
private readonly int _symmetricAlgorithmBlockSizeInBytes; | |
private readonly int _symmetricAlgorithmSubkeyLengthInBytes; | |
private readonly int _validationAlgorithmDigestLengthInBytes; | |
private readonly int _validationAlgorithmSubkeyLengthInBytes; | |
private readonly Func<KeyedHashAlgorithm> _validationAlgorithmFactory; | |
public ManagedAuthenticatedEncryptor(byte[] keyDerivationKey, Func<SymmetricAlgorithm> symmetricAlgorithmFactory, int symmetricAlgorithmKeySizeInBytes, Func<KeyedHashAlgorithm> validationAlgorithmFactory) | |
{ | |
//_genRandom = genRandom ?? ManagedGenRandomImpl.Instance; | |
_keyDerivationKey = keyDerivationKey; | |
// Validate that the symmetric algorithm has the properties we require | |
using (var symmetricAlgorithm = symmetricAlgorithmFactory()) | |
{ | |
_symmetricAlgorithmFactory = symmetricAlgorithmFactory; | |
_symmetricAlgorithmBlockSizeInBytes = symmetricAlgorithm.BlockSize / 8; | |
_symmetricAlgorithmSubkeyLengthInBytes = symmetricAlgorithmKeySizeInBytes; | |
} | |
// Validate that the MAC algorithm has the properties we require | |
using (var validationAlgorithm = validationAlgorithmFactory()) | |
{ | |
_validationAlgorithmFactory = validationAlgorithmFactory; | |
_validationAlgorithmDigestLengthInBytes = validationAlgorithm.HashSize / 8; | |
_validationAlgorithmSubkeyLengthInBytes = _validationAlgorithmDigestLengthInBytes; // for simplicity we'll generate MAC subkeys with a length equal to the digest length | |
} | |
_contextHeader = CreateContextHeader(); | |
} | |
private byte[] CreateContextHeader() | |
{ | |
var EMPTY_ARRAY = new byte[0]; | |
var EMPTY_ARRAY_SEGMENT = new ArraySegment<byte>(EMPTY_ARRAY); | |
var retVal = new byte[checked( | |
1 /* KDF alg */ | |
+ 1 /* chaining mode */ | |
+ sizeof(uint) /* sym alg key size */ | |
+ sizeof(uint) /* sym alg block size */ | |
+ sizeof(uint) /* hmac alg key size */ | |
+ sizeof(uint) /* hmac alg digest size */ | |
+ _symmetricAlgorithmBlockSizeInBytes /* ciphertext of encrypted empty string */ | |
+ _validationAlgorithmDigestLengthInBytes /* digest of HMACed empty string */)]; | |
var idx = 0; | |
// First is the two-byte header | |
retVal[idx++] = 0; // 0x00 = SP800-108 CTR KDF w/ HMACSHA512 PRF | |
retVal[idx++] = 0; // 0x00 = CBC encryption + HMAC authentication | |
// Next is information about the symmetric algorithm (key size followed by block size) | |
BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmSubkeyLengthInBytes); | |
BitHelpers.WriteTo(retVal, ref idx, _symmetricAlgorithmBlockSizeInBytes); | |
// Next is information about the keyed hash algorithm (key size followed by digest size) | |
BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmSubkeyLengthInBytes); | |
BitHelpers.WriteTo(retVal, ref idx, _validationAlgorithmDigestLengthInBytes); | |
// See the design document for an explanation of the following code. | |
var tempKeys = new byte[_symmetricAlgorithmSubkeyLengthInBytes + _validationAlgorithmSubkeyLengthInBytes]; | |
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys( | |
kdk: EMPTY_ARRAY, | |
label: EMPTY_ARRAY_SEGMENT, | |
context: EMPTY_ARRAY_SEGMENT, | |
prfFactory: _kdkPrfFactory, | |
output: new ArraySegment<byte>(tempKeys)); | |
// At this point, tempKeys := { K_E || K_H }. | |
// Encrypt a zero-length input string with an all-zero IV and copy the ciphertext to the return buffer. | |
using (var symmetricAlg = CreateSymmetricAlgorithm()) | |
{ | |
using (var cryptoTransform = symmetricAlg.CreateEncryptor( | |
rgbKey: new ArraySegment<byte>(tempKeys, 0, _symmetricAlgorithmSubkeyLengthInBytes).ToArray(), | |
rgbIV: new byte[_symmetricAlgorithmBlockSizeInBytes])) | |
{ | |
var ciphertext = cryptoTransform.TransformFinalBlock(EMPTY_ARRAY, 0, 0); | |
//CryptoUtil.Assert(ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes, "ciphertext != null && ciphertext.Length == _symmetricAlgorithmBlockSizeInBytes"); | |
Buffer.BlockCopy(ciphertext, 0, retVal, idx, ciphertext.Length); | |
} | |
} | |
idx += _symmetricAlgorithmBlockSizeInBytes; | |
// MAC a zero-length input string and copy the digest to the return buffer. | |
using (var hashAlg = CreateValidationAlgorithm(new ArraySegment<byte>(tempKeys, _symmetricAlgorithmSubkeyLengthInBytes, _validationAlgorithmSubkeyLengthInBytes).ToArray())) | |
{ | |
var digest = hashAlg.ComputeHash(EMPTY_ARRAY); | |
//CryptoUtil.Assert(digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes, "digest != null && digest.Length == _validationAlgorithmDigestLengthInBytes"); | |
Buffer.BlockCopy(digest, 0, retVal, idx, digest.Length); | |
} | |
idx += _validationAlgorithmDigestLengthInBytes; | |
//CryptoUtil.Assert(idx == retVal.Length, "idx == retVal.Length"); | |
// retVal := { version || chainingMode || symAlgKeySize || symAlgBlockSize || macAlgKeySize || macAlgDigestSize || E("") || MAC("") }. | |
return retVal; | |
} | |
private SymmetricAlgorithm CreateSymmetricAlgorithm() | |
{ | |
var retVal = _symmetricAlgorithmFactory(); | |
retVal.Mode = CipherMode.CBC; //Important! This is the correct CipherMode for cookie sharing | |
retVal.Padding = PaddingMode.PKCS7; //Important! This is the correct PaddingMode for cookie sharing | |
//NB: If you get "invalid padding" errors, it means that your decryption key was wrong. Changing the padding will just output garbage anyway. | |
return retVal; | |
} | |
private KeyedHashAlgorithm CreateValidationAlgorithm(byte[] key) | |
{ | |
var retVal = _validationAlgorithmFactory(); | |
retVal.Key = key; | |
return retVal; | |
} | |
public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> additionalAuthenticatedData) | |
{ | |
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC | |
if (protectedPayload.Count < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _validationAlgorithmDigestLengthInBytes)) | |
{ | |
throw new Exception(); | |
} | |
// Assumption: protectedPayload := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) } | |
try | |
{ | |
// Step 1: Extract the key modifier and IV from the payload. | |
int keyModifierOffset; // position in protectedPayload.Array where key modifier begins | |
int ivOffset; // position in protectedPayload.Array where key modifier ends / IV begins | |
int ciphertextOffset; // position in protectedPayload.Array where IV ends / ciphertext begins | |
int macOffset; // position in protectedPayload.Array where ciphertext ends / MAC begins | |
int eofOffset; // position in protectedPayload.Array where MAC ends | |
checked | |
{ | |
keyModifierOffset = protectedPayload.Offset; | |
ivOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES; | |
ciphertextOffset = ivOffset + _symmetricAlgorithmBlockSizeInBytes; | |
} | |
ArraySegment<byte> keyModifier = new ArraySegment<byte>(protectedPayload.Array, keyModifierOffset, ivOffset - keyModifierOffset); | |
var iv = new byte[_symmetricAlgorithmBlockSizeInBytes]; | |
Buffer.BlockCopy(protectedPayload.Array, ivOffset, iv, 0, iv.Length); | |
// Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys. | |
// We pin all unencrypted keys to limit their exposure via GC relocation. | |
var decryptedKdk = new byte[_keyDerivationKey.Length]; | |
var decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes]; | |
var validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes]; | |
var derivedKeysBuffer = new byte[checked(decryptionSubkey.Length + validationSubkey.Length)]; | |
fixed (byte* __unused__1 = decryptedKdk) | |
fixed (byte* __unused__2 = decryptionSubkey) | |
fixed (byte* __unused__3 = validationSubkey) | |
fixed (byte* __unused__4 = derivedKeysBuffer) | |
{ | |
decryptedKdk = _keyDerivationKey; | |
ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( | |
kdk: decryptedKdk, | |
label: additionalAuthenticatedData, | |
contextHeader: _contextHeader, | |
context: keyModifier, | |
prfFactory: _kdkPrfFactory, | |
output: new ArraySegment<byte>(derivedKeysBuffer)); | |
//This is the 32byte AES Key derived from the KDK | |
Buffer.BlockCopy(derivedKeysBuffer, 0, decryptionSubkey, 0, decryptionSubkey.Length); | |
//This is the 32byte SHA256 Key derived from the KDK | |
Buffer.BlockCopy(derivedKeysBuffer, decryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length); | |
// Step 3: Calculate the correct MAC for this payload. | |
// correctHash := MAC(IV || ciphertext) | |
byte[] correctHash; | |
using (var hashAlgorithm = CreateValidationAlgorithm(validationSubkey)) | |
{ | |
checked | |
{ | |
eofOffset = protectedPayload.Offset + protectedPayload.Count; | |
macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes; | |
} | |
correctHash = hashAlgorithm.ComputeHash(protectedPayload.Array, ivOffset, macOffset - ivOffset); | |
} | |
// Step 4: Validate the MAC provided as part of the payload. | |
int i = 0; | |
foreach (var b in correctHash) | |
{ | |
var mb = protectedPayload.Array[macOffset + i++]; | |
Debug.Assert(b == mb); | |
} | |
// Step 5: Decipher the ciphertext and return it to the caller. | |
using (var symmetricAlgorithm = CreateSymmetricAlgorithm()) | |
using (var cryptoTransform = symmetricAlgorithm.CreateDecryptor(decryptionSubkey, iv)) | |
{ | |
var outputStream = new MemoryStream(); | |
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write)) | |
{ | |
cryptoStream.Write(protectedPayload.Array, ciphertextOffset, macOffset - ciphertextOffset); | |
cryptoStream.FlushFinalBlock(); | |
// At this point, outputStream := { plaintext }, and we're done! | |
return outputStream.ToArray(); | |
} | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
throw ex; | |
} | |
} | |
internal static class ManagedSP800_108_CTR_HMACSHA512 | |
{ | |
public static void DeriveKeys(byte[] kdk, ArraySegment<byte> label, ArraySegment<byte> context, Func<byte[], HashAlgorithm> prfFactory, ArraySegment<byte> output) | |
{ | |
// make copies so we can mutate these local vars | |
var outputOffset = output.Offset; | |
var outputCount = output.Count; | |
using (var prf = prfFactory(kdk)) | |
{ | |
// See SP800-108, Sec. 5.1 for the format of the input to the PRF routine. | |
var prfInput = new byte[checked(sizeof(uint) /* [i]_2 */ + label.Count + 1 /* 0x00 */ + context.Count + sizeof(uint) /* [K]_2 */)]; | |
// Copy [L]_2 to prfInput since it's stable over all iterations | |
uint outputSizeInBits = (uint)checked((int)outputCount * 8); | |
prfInput[prfInput.Length - 4] = (byte)(outputSizeInBits >> 24); | |
prfInput[prfInput.Length - 3] = (byte)(outputSizeInBits >> 16); | |
prfInput[prfInput.Length - 2] = (byte)(outputSizeInBits >> 8); | |
prfInput[prfInput.Length - 1] = (byte)(outputSizeInBits); | |
// Copy label and context to prfInput since they're stable over all iterations | |
Buffer.BlockCopy(label.Array, label.Offset, prfInput, sizeof(uint), label.Count); | |
Buffer.BlockCopy(context.Array, context.Offset, prfInput, sizeof(int) + label.Count + 1, context.Count); | |
var prfOutputSizeInBytes = prf.HashSize / 8;//.GetDigestSizeInBytes(); | |
for (uint i = 1; outputCount > 0; i++) | |
{ | |
// Copy [i]_2 to prfInput since it mutates with each iteration | |
prfInput[0] = (byte)(i >> 24); | |
prfInput[1] = (byte)(i >> 16); | |
prfInput[2] = (byte)(i >> 8); | |
prfInput[3] = (byte)(i); | |
// Run the PRF and copy the results to the output buffer | |
var prfOutput = prf.ComputeHash(prfInput); | |
Debug.Assert(prfOutputSizeInBytes == prfOutput.Length, "prfOutputSizeInBytes == prfOutput.Length"); | |
var numBytesToCopyThisIteration = Math.Min(prfOutputSizeInBytes, outputCount); | |
Buffer.BlockCopy(prfOutput, 0, output.Array, outputOffset, numBytesToCopyThisIteration); | |
Array.Clear(prfOutput, 0, prfOutput.Length); // contains key material, so delete it | |
// adjust offsets | |
outputOffset += numBytesToCopyThisIteration; | |
outputCount -= numBytesToCopyThisIteration; | |
} | |
} | |
} | |
public static void DeriveKeysWithContextHeader(byte[] kdk, ArraySegment<byte> label, byte[] contextHeader, ArraySegment<byte> context, Func<byte[], HashAlgorithm> prfFactory, ArraySegment<byte> output) | |
{ | |
var combinedContext = new byte[checked(contextHeader.Length + context.Count)]; | |
Buffer.BlockCopy(contextHeader, 0, combinedContext, 0, contextHeader.Length); | |
Buffer.BlockCopy(context.Array, context.Offset, combinedContext, contextHeader.Length, context.Count); | |
DeriveKeys(kdk, label, new ArraySegment<byte>(combinedContext), prfFactory, output); | |
} | |
} | |
internal static class BitHelpers | |
{ | |
/// <summary> | |
/// Writes an unsigned 32-bit value to a memory address, big-endian. | |
/// </summary> | |
public static void WriteTo(void* ptr, uint value) | |
{ | |
byte* bytePtr = (byte*)ptr; | |
bytePtr[0] = (byte)(value >> 24); | |
bytePtr[1] = (byte)(value >> 16); | |
bytePtr[2] = (byte)(value >> 8); | |
bytePtr[3] = (byte)(value); | |
} | |
public static void WriteTo(ref byte* ptr, uint value) | |
{ | |
byte* pTemp = ptr; | |
pTemp[0] = (byte)(value >> 24); | |
pTemp[1] = (byte)(value >> 16); | |
pTemp[2] = (byte)(value >> 8); | |
pTemp[3] = (byte)(value); | |
ptr = &pTemp[4]; | |
} | |
/// <summary> | |
/// Writes a signed 32-bit value to a memory address, big-endian. | |
/// </summary> | |
public static void WriteTo(byte[] buffer, ref int idx, int value) | |
{ | |
WriteTo(buffer, ref idx, (uint)value); | |
} | |
/// <summary> | |
/// Writes a signed 32-bit value to a memory address, big-endian. | |
/// </summary> | |
public static void WriteTo(byte[] buffer, ref int idx, uint value) | |
{ | |
buffer[idx++] = (byte)(value >> 24); | |
buffer[idx++] = (byte)(value >> 16); | |
buffer[idx++] = (byte)(value >> 8); | |
buffer[idx++] = (byte)(value); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment