Skip to content

Commit d090882

Browse files
committed
finish encrypt
1 parent eb6cb1b commit d090882

File tree

2 files changed

+185
-113
lines changed

2 files changed

+185
-113
lines changed

src/DataProtection/DataProtection/src/Managed/ManagedAuthenticatedEncryptor.cs

Lines changed: 117 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Buffers;
56
using System.IO;
67
using System.Linq;
78
using System.Security.Cryptography;
@@ -153,15 +154,12 @@ private SymmetricAlgorithm CreateSymmetricAlgorithm(byte[]? key = null)
153154
return retVal;
154155
}
155156

156-
private KeyedHashAlgorithm CreateValidationAlgorithm(byte[]? key = null)
157+
private KeyedHashAlgorithm CreateValidationAlgorithm(byte[] key)
157158
{
158159
var retVal = _validationAlgorithmFactory();
159160
CryptoUtil.Assert(retVal != null, "retVal != null");
160161

161-
if (key is not null)
162-
{
163-
retVal.Key = key;
164-
}
162+
retVal.Key = key;
165163
return retVal;
166164
}
167165

@@ -298,29 +296,7 @@ public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additiona
298296

299297
try
300298
{
301-
var decryptedKdk = new byte[_keyDerivationKey.Length];
302-
var encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
303-
var validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
304-
var derivedKeysBuffer = new byte[checked(encryptionSubkey.Length + validationSubkey.Length)];
305-
306-
fixed (byte* __unused__1 = decryptedKdk)
307-
fixed (byte* __unused__2 = encryptionSubkey)
308-
fixed (byte* __unused__3 = validationSubkey)
309-
fixed (byte* __unused__4 = derivedKeysBuffer)
310-
{
311-
try
312-
{
313-
return EncryptImpl(plaintext, additionalAuthenticatedData.AsSpan(), decryptedKdk, encryptionSubkey, validationSubkey, derivedKeysBuffer);
314-
}
315-
finally
316-
{
317-
// delete since these contain secret material
318-
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
319-
Array.Clear(encryptionSubkey, 0, encryptionSubkey.Length);
320-
Array.Clear(validationSubkey, 0, validationSubkey.Length);
321-
Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length);
322-
}
323-
}
299+
return EncryptImpl(plaintext, additionalAuthenticatedData);
324300
}
325301
catch (Exception ex) when (ex.RequiresHomogenization())
326302
{
@@ -331,75 +307,79 @@ public byte[] Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additiona
331307

332308
#if NET10_0_OR_GREATER
333309
private byte[] EncryptImpl(
334-
ArraySegment<byte> plaintextArraySegment,
335-
ReadOnlySpan<byte> additionalAuthenticatedData,
336-
byte[] decryptedKdk,
337-
byte[] encryptionSubkey,
338-
byte[] validationSubkey,
339-
byte[] derivedKeysBuffer)
310+
ReadOnlySpan<byte> plainText,
311+
ReadOnlySpan<byte> additionalAuthenticatedData)
340312
{
341-
var plainText = plaintextArraySegment.AsSpan();
342-
343-
// Step 4: Perform the encryption operation.
344-
345-
using var symmetricAlgorithm = CreateSymmetricAlgorithm(key: encryptionSubkey);
346-
using var validationAlgorithm = CreateValidationAlgorithm(key: null!); // we dont care now, because we are only interested in the hash size property
347-
348-
var cipherTextLength = symmetricAlgorithm.GetCiphertextLengthCbc(plainText.Length);
349313
var keyModifierLength = KEY_MODIFIER_SIZE_IN_BYTES;
350314
var ivLength = _symmetricAlgorithmBlockSizeInBytes;
351-
var macLength = validationAlgorithm.HashSize * 8;
352-
353-
// allocating an array of a specific required length:
354-
var outputArray = new byte[keyModifierLength + ivLength + cipherTextLength + macLength];
355-
var outputSpan = outputArray.AsSpan();
356-
357-
// Step 1: Generate a random key modifier and IV for this operation.
358-
// Both will be equal to the block size of the block cipher algorithm.
359-
// note: we are generating it right into the result array
360-
_genRandom.GenRandom(outputSpan.Slice(start: 0, length: keyModifierLength));
361-
_genRandom.GenRandom(outputSpan.Slice(start: keyModifierLength, length: ivLength));
362315

363-
var keyModifier = outputSpan.Slice(start: 0, length: keyModifierLength);
364-
var iv = outputSpan.Slice(start: keyModifierLength, length: ivLength);
365-
366-
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
367-
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
368-
kdk: decryptedKdk,
369-
label: additionalAuthenticatedData,
370-
contextHeader: _contextHeader,
371-
contextData: keyModifier,
372-
prfFactory: _kdkPrfFactory,
373-
output: new ArraySegment<byte>(derivedKeysBuffer));
374-
375-
Buffer.BlockCopy(derivedKeysBuffer, 0, encryptionSubkey, 0, encryptionSubkey.Length);
376-
Buffer.BlockCopy(derivedKeysBuffer, encryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length);
316+
// Step 1: Decrypt the KDK, and use it to generate new encryption and HMAC keys.
317+
// We pin all unencrypted keys to limit their exposure via GC relocation.
318+
var decryptedKdk = new byte[_keyDerivationKey.Length];
319+
var encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
320+
var validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
377321

378-
// encrypting plaintext into the target array directly
379-
symmetricAlgorithm.EncryptCbc(plainText, iv, outputSpan.Slice(start: KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes, length: cipherTextLength));
322+
fixed (byte* __unused__1 = decryptedKdk)
323+
fixed (byte* __unused__2 = encryptionSubkey)
324+
fixed (byte* __unused__3 = validationSubkey)
325+
{
326+
var keyModifier = ArrayPool<byte>.Shared.Rent(keyModifierLength);
327+
_genRandom.GenRandom(keyModifier);
328+
329+
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
330+
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
331+
kdk: decryptedKdk,
332+
label: additionalAuthenticatedData,
333+
contextHeader: _contextHeader,
334+
contextData: keyModifier,
335+
prfFactory: _kdkPrfFactory,
336+
operationSubKey: encryptionSubkey,
337+
validationSubKey: validationSubkey);
338+
339+
// idea of optimization here is firstly get all the types preset
340+
// for calculating length of the output array and allocating it.
341+
// then we are filling it with the data directly, without any additional copying
342+
using var symmetricAlgorithm = CreateSymmetricAlgorithm(key: encryptionSubkey);
343+
using var validationAlgorithm = CreateValidationAlgorithm(key: validationSubkey);
344+
345+
var cipherTextLength = symmetricAlgorithm.GetCiphertextLengthCbc(plainText.Length); // CBC because symmetricAlgorithm is created with CBC mode
346+
var macLength = _validationAlgorithmDigestLengthInBytes;
347+
348+
// allocating an array of a specific required length
349+
var outputArray = new byte[keyModifierLength + ivLength + cipherTextLength + macLength];
350+
var outputSpan = outputArray.AsSpan();
351+
352+
// Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header.
353+
keyModifier.CopyTo(outputSpan.Slice(start: 0, length: keyModifierLength));
354+
355+
// Step 3: Generate IV for this operation right into the result array (no allocation)
356+
_genRandom.GenRandom(outputSpan.Slice(start: keyModifierLength, length: ivLength));
357+
var iv = outputSpan.Slice(start: keyModifierLength, length: ivLength);
358+
359+
// encrypting plaintext into the target array directly
360+
symmetricAlgorithm.EncryptCbc(plainText, iv, outputSpan.Slice(start: keyModifierLength + ivLength, length: cipherTextLength));
380361

381-
// At this point, outputStream := { keyModifier || IV || ciphertext }
362+
// At this point, outputStream := { keyModifier || IV || ciphertext }
382363

383-
// Step 5: Calculate the digest over the IV and ciphertext.
384-
// We don't need to calculate the digest over the key modifier since that
385-
// value has already been mixed into the KDF used to generate the MAC key.
364+
// Step 4: Calculate the digest over the IV and ciphertext.
365+
// We don't need to calculate the digest over the key modifier since that
366+
// value has already been mixed into the KDF used to generate the MAC key.
386367

387-
validationAlgorithm.Key = validationSubkey; // crucial: finally set the key
388-
validationAlgorithm.TryComputeHash(source: outputSpan, destination: outputSpan.Slice(start: keyModifierLength, length: ivLength + cipherTextLength), bytesWritten: out _);
368+
var ivAndCipherTextSpan = outputSpan.Slice(start: keyModifierLength, length: ivLength + cipherTextLength);
369+
var macDestinationSpan = outputSpan.Slice(keyModifierLength + ivLength + cipherTextLength, macLength);
370+
validationAlgorithm.TryComputeHash(source: ivAndCipherTextSpan, destination: macDestinationSpan, bytesWritten: out _);
371+
// At this point, outputArray := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
389372

390-
// At this point, outputArray := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
391-
// And we're done!
373+
// returning whatever was pooled back with clear (secret data to be cleaned)
374+
ArrayPool<byte>.Shared.Return(keyModifier, clearArray: true);
392375

393-
return outputArray;
376+
return outputArray;
377+
}
394378
}
395379
#else
396380
private byte[] EncryptImpl(
397381
ArraySegment<byte> plaintext,
398-
ReadOnlySpan<byte> additionalAuthenticatedData,
399-
byte[] decryptedKdk,
400-
byte[] encryptionSubkey,
401-
byte[] validationSubkey,
402-
byte[] derivedKeysBuffer)
382+
ArraySegment<byte> additionalAuthenticatedData)
403383
{
404384
var outputStream = new MemoryStream();
405385

@@ -415,46 +395,70 @@ private byte[] EncryptImpl(
415395
outputStream.Write(iv, 0, iv.Length);
416396

417397
// At this point, outputStream := { keyModifier || IV }.
398+
418399
// Step 3: Decrypt the KDK, and use it to generate new encryption and HMAC keys.
419400
// We pin all unencrypted keys to limit their exposure via GC relocation.
420-
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
421-
ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(
422-
kdk: decryptedKdk,
423-
label: additionalAuthenticatedData,
424-
contextHeader: _contextHeader,
425-
contextData: new ArraySegment<byte>(keyModifier),
426-
prfFactory: _kdkPrfFactory,
427-
output: new ArraySegment<byte>(derivedKeysBuffer));
428-
429-
Buffer.BlockCopy(derivedKeysBuffer, 0, encryptionSubkey, 0, encryptionSubkey.Length);
430-
Buffer.BlockCopy(derivedKeysBuffer, encryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length);
431401

432-
// Step 4: Perform the encryption operation.
402+
var decryptedKdk = new byte[_keyDerivationKey.Length];
403+
var encryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
404+
var validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
405+
var derivedKeysBuffer = new byte[checked(encryptionSubkey.Length + validationSubkey.Length)];
433406

434-
using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
435-
using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
436-
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
407+
fixed (byte* __unused__1 = decryptedKdk)
408+
fixed (byte* __unused__2 = encryptionSubkey)
409+
fixed (byte* __unused__3 = validationSubkey)
410+
fixed (byte* __unused__4 = derivedKeysBuffer)
437411
{
438-
cryptoStream.Write(plaintext.Array!, plaintext.Offset, plaintext.Count);
439-
cryptoStream.FlushFinalBlock();
412+
try
413+
{
414+
_keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment<byte>(decryptedKdk));
415+
ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader(
416+
kdk: decryptedKdk,
417+
label: additionalAuthenticatedData,
418+
contextHeader: _contextHeader,
419+
context: new ArraySegment<byte>(keyModifier),
420+
prfFactory: _kdkPrfFactory,
421+
output: new ArraySegment<byte>(derivedKeysBuffer));
422+
423+
Buffer.BlockCopy(derivedKeysBuffer, 0, encryptionSubkey, 0, encryptionSubkey.Length);
424+
Buffer.BlockCopy(derivedKeysBuffer, encryptionSubkey.Length, validationSubkey, 0, validationSubkey.Length);
425+
426+
// Step 4: Perform the encryption operation.
427+
428+
using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
429+
using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
430+
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
431+
{
432+
cryptoStream.Write(plaintext.Array!, plaintext.Offset, plaintext.Count);
433+
cryptoStream.FlushFinalBlock();
440434

441-
// At this point, outputStream := { keyModifier || IV || ciphertext }
435+
// At this point, outputStream := { keyModifier || IV || ciphertext }
442436

443-
// Step 5: Calculate the digest over the IV and ciphertext.
444-
// We don't need to calculate the digest over the key modifier since that
445-
// value has already been mixed into the KDF used to generate the MAC key.
437+
// Step 5: Calculate the digest over the IV and ciphertext.
438+
// We don't need to calculate the digest over the key modifier since that
439+
// value has already been mixed into the KDF used to generate the MAC key.
446440

447-
using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkey))
448-
{
449-
// As an optimization, avoid duplicating the underlying buffer
450-
var underlyingBuffer = outputStream.GetBuffer();
441+
using (var validationAlgorithm = CreateValidationAlgorithm(validationSubkey))
442+
{
443+
// As an optimization, avoid duplicating the underlying buffer
444+
var underlyingBuffer = outputStream.GetBuffer();
451445

452-
var mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES));
453-
outputStream.Write(mac, 0, mac.Length);
446+
var mac = validationAlgorithm.ComputeHash(underlyingBuffer, KEY_MODIFIER_SIZE_IN_BYTES, checked((int)outputStream.Length - KEY_MODIFIER_SIZE_IN_BYTES));
447+
outputStream.Write(mac, 0, mac.Length);
454448

455-
// At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
456-
// And we're done!
457-
return outputStream.ToArray();
449+
// At this point, outputStream := { keyModifier || IV || ciphertext || MAC(IV || ciphertext) }
450+
// And we're done!
451+
return outputStream.ToArray();
452+
}
453+
}
454+
}
455+
finally
456+
{
457+
// delete since these contain secret material
458+
Array.Clear(decryptedKdk, 0, decryptedKdk.Length);
459+
Array.Clear(encryptionSubkey, 0, encryptionSubkey.Length);
460+
Array.Clear(validationSubkey, 0, validationSubkey.Length);
461+
Array.Clear(derivedKeysBuffer, 0, derivedKeysBuffer.Length);
458462
}
459463
}
460464
}

src/DataProtection/DataProtection/src/SP800_108/ManagedSP800_108_CTR_HMACSHA512.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,74 @@ public static void DeriveKeys(byte[] kdk, ReadOnlySpan<byte> label, ReadOnlySpan
104104
}
105105
}
106106

107+
public static void DeriveKeys(
108+
byte[] kdk,
109+
ReadOnlySpan<byte> label,
110+
ReadOnlySpan<byte> contextHeader,
111+
ReadOnlySpan<byte> contextData,
112+
Func<byte[], HashAlgorithm> prfFactory,
113+
Span<byte> operationSubKey,
114+
Span<byte> validationSubKey)
115+
{
116+
var operationSubKeyIndex = 0;
117+
var validationSubKeyIndex = 0;
118+
var outputCount = operationSubKey.Length + validationSubKey.Length;
119+
120+
using (var prf = prfFactory(kdk))
121+
{
122+
// See SP800-108, Sec. 5.1 for the format of the input to the PRF routine.
123+
var prfInput = new byte[checked(sizeof(uint) /* [i]_2 */ + label.Length + 1 /* 0x00 */ + (contextHeader.Length + contextData.Length) + sizeof(uint) /* [K]_2 */)];
124+
125+
// Copy [L]_2 to prfInput since it's stable over all iterations
126+
uint outputSizeInBits = (uint)checked((int)outputCount * 8);
127+
prfInput[prfInput.Length - 4] = (byte)(outputSizeInBits >> 24);
128+
prfInput[prfInput.Length - 3] = (byte)(outputSizeInBits >> 16);
129+
prfInput[prfInput.Length - 2] = (byte)(outputSizeInBits >> 8);
130+
prfInput[prfInput.Length - 1] = (byte)(outputSizeInBits);
131+
132+
// Copy label and context to prfInput since they're stable over all iterations
133+
label.CopyTo(prfInput.AsSpan(sizeof(uint)));
134+
contextHeader.CopyTo(prfInput.AsSpan(sizeof(uint) + label.Length + 1));
135+
contextData.CopyTo(prfInput.AsSpan(sizeof(uint) + label.Length + 1 + contextHeader.Length));
136+
137+
var prfOutputSizeInBytes = prf.GetDigestSizeInBytes();
138+
for (uint i = 1; outputCount > 0; i++)
139+
{
140+
// Copy [i]_2 to prfInput since it mutates with each iteration
141+
prfInput[0] = (byte)(i >> 24);
142+
prfInput[1] = (byte)(i >> 16);
143+
prfInput[2] = (byte)(i >> 8);
144+
prfInput[3] = (byte)(i);
145+
146+
// Run the PRF and copy the results to the output buffer
147+
var prfOutput = prf.ComputeHash(prfInput);
148+
CryptoUtil.Assert(prfOutputSizeInBytes == prfOutput.Length, "prfOutputSizeInBytes == prfOutput.Length");
149+
var numBytesToCopyThisIteration = Math.Min(prfOutputSizeInBytes, outputCount);
150+
151+
// we need to write into the operationSubkey
152+
// but it may be the case that we need to split the output
153+
// so lets count how many bytes we can write into the operationSubKey
154+
var bytesToWrite = Math.Min(numBytesToCopyThisIteration, operationSubKey.Length - operationSubKeyIndex);
155+
var leftOverBytes = numBytesToCopyThisIteration - bytesToWrite;
156+
if (operationSubKeyIndex < operationSubKey.Length) // meaning we need to write to operationSubKey
157+
{
158+
var destination = operationSubKey.Slice(operationSubKeyIndex, bytesToWrite);
159+
prfOutput.AsSpan().Slice(0, bytesToWrite).CopyTo(destination);
160+
operationSubKeyIndex += bytesToWrite;
161+
}
162+
if (operationSubKeyIndex == operationSubKey.Length && leftOverBytes != 0) // we have filled the operationSubKey. It's time for the validationSubKey
163+
{
164+
var destination = validationSubKey.Slice(validationSubKeyIndex, leftOverBytes);
165+
prfOutput.AsSpan().Slice(bytesToWrite, leftOverBytes).CopyTo(destination);
166+
validationSubKeyIndex += leftOverBytes;
167+
}
168+
169+
Array.Clear(prfOutput, 0, prfOutput.Length); // contains key material, so delete it
170+
outputCount -= numBytesToCopyThisIteration;
171+
}
172+
}
173+
}
174+
107175
public static void DeriveKeysWithContextHeader(byte[] kdk, ArraySegment<byte> label, byte[] contextHeader, ArraySegment<byte> context, Func<byte[], HashAlgorithm> prfFactory, ArraySegment<byte> output)
108176
{
109177
var combinedContext = new byte[checked(contextHeader.Length + context.Count)];

0 commit comments

Comments
 (0)