Skip to content

Commit 29e7622

Browse files
committed
cbc
1 parent 72aecba commit 29e7622

File tree

1 file changed

+118
-55
lines changed

1 file changed

+118
-55
lines changed

src/DataProtection/DataProtection/src/Cng/CbcAuthenticatedEncryptor.cs

Lines changed: 118 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -57,79 +57,140 @@ public CbcAuthenticatedEncryptor(Secret keyDerivationKey, BCryptAlgorithmHandle
5757
}
5858

5959
public int GetDecryptedSize(int cipherTextLength)
60-
{
61-
throw new NotImplementedException();
62-
}
63-
64-
public bool TryDecrypt(ReadOnlySpan<byte> cipherText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
65-
{
66-
throw new NotImplementedException();
67-
}
68-
69-
public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
70-
{
71-
throw new NotImplementedException();
72-
}
73-
74-
protected override byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
7560
{
7661
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
77-
if (cbCiphertext < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
62+
if (cipherTextLength < checked(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes))
7863
{
7964
throw Error.CryptCommon_PayloadInvalid();
8065
}
8166

82-
// Assumption: pbCipherText := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
83-
84-
var cbEncryptedData = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
67+
return checked(cipherTextLength - (int)(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + _hmacAlgorithmDigestLengthInBytes));
68+
}
8569

86-
// Calculate offsets
87-
byte* pbKeyModifier = pbCiphertext;
88-
byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
89-
byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
90-
byte* pbActualHmac = &pbEncryptedData[cbEncryptedData];
70+
public bool TryDecrypt(ReadOnlySpan<byte> cipherText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
71+
{
72+
bytesWritten = 0;
9173

92-
// Use the KDF to recreate the symmetric encryption and HMAC subkeys
93-
// We'll need a temporary buffer to hold them
94-
var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
95-
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
9674
try
9775
{
98-
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
99-
pbLabel: pbAdditionalAuthenticatedData,
100-
cbLabel: cbAdditionalAuthenticatedData,
101-
contextHeader: _contextHeader,
102-
pbContext: pbKeyModifier,
103-
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
104-
pbDerivedKey: pbTempSubkeys,
105-
cbDerivedKey: cbTempSubkeys);
106-
107-
// Calculate offsets
108-
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
109-
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
110-
111-
// First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
112-
// data hasn't been tampered with. The integrity check is also implicitly performed over
113-
// keyModifier since that value was provided to the KDF earlier.
114-
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
76+
var cbEncryptedData = GetDecryptedSize(cipherText.Length);
77+
78+
// Assumption: cipherText := { keyModifier | IV | encryptedData | MAC(IV | encryptedPayload) }
79+
fixed (byte* pbCiphertext = cipherText)
80+
fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
81+
fixed (byte* pbDestination = destination)
11582
{
116-
if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + cbEncryptedData, pbActualHmac))
83+
// Calculate offsets
84+
byte* pbKeyModifier = pbCiphertext;
85+
byte* pbIV = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
86+
byte* pbEncryptedData = &pbIV[_symmetricAlgorithmBlockSizeInBytes];
87+
byte* pbActualHmac = &pbEncryptedData[cbEncryptedData];
88+
89+
// Use the KDF to recreate the symmetric encryption and HMAC subkeys
90+
// We'll need a temporary buffer to hold them
91+
var cbTempSubkeys = checked(_symmetricAlgorithmSubkeyLengthInBytes + _hmacAlgorithmSubkeyLengthInBytes);
92+
byte* pbTempSubkeys = stackalloc byte[checked((int)cbTempSubkeys)];
93+
try
11794
{
118-
throw Error.CryptCommon_PayloadInvalid();
95+
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
96+
pbLabel: pbAdditionalAuthenticatedData,
97+
cbLabel: (uint)additionalAuthenticatedData.Length,
98+
contextHeader: _contextHeader,
99+
pbContext: pbKeyModifier,
100+
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
101+
pbDerivedKey: pbTempSubkeys,
102+
cbDerivedKey: cbTempSubkeys);
103+
104+
// Calculate offsets
105+
byte* pbSymmetricEncryptionSubkey = pbTempSubkeys;
106+
byte* pbHmacSubkey = &pbTempSubkeys[_symmetricAlgorithmSubkeyLengthInBytes];
107+
108+
// First, perform an explicit integrity check over (iv | encryptedPayload) to ensure the
109+
// data hasn't been tampered with. The integrity check is also implicitly performed over
110+
// keyModifier since that value was provided to the KDF earlier.
111+
using (var hashHandle = _hmacAlgorithmHandle.CreateHmac(pbHmacSubkey, _hmacAlgorithmSubkeyLengthInBytes))
112+
{
113+
if (!ValidateHash(hashHandle, pbIV, _symmetricAlgorithmBlockSizeInBytes + (uint)cbEncryptedData, pbActualHmac))
114+
{
115+
throw Error.CryptCommon_PayloadInvalid();
116+
}
117+
}
118+
119+
// If the integrity check succeeded, decrypt the payload.
120+
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
121+
{
122+
// BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
123+
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
124+
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
125+
126+
// Perform the decryption directly into destination
127+
uint dwActualDecryptedByteCount;
128+
byte dummy;
129+
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
130+
hKey: decryptionSubkeyHandle,
131+
pbInput: pbEncryptedData,
132+
cbInput: (uint)cbEncryptedData,
133+
pPaddingInfo: null,
134+
pbIV: pbClonedIV,
135+
cbIV: _symmetricAlgorithmBlockSizeInBytes,
136+
pbOutput: (destination.Length > 0) ? pbDestination : &dummy,
137+
cbOutput: (uint)destination.Length,
138+
pcbResult: out dwActualDecryptedByteCount,
139+
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
140+
141+
// Check for buffer too small before throwing other exceptions
142+
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
143+
if (ntstatus == unchecked((int)0xC0000023)) // STATUS_BUFFER_TOO_SMALL
144+
{
145+
return false;
146+
}
147+
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
148+
149+
bytesWritten = checked((int)dwActualDecryptedByteCount);
150+
return true;
151+
}
152+
}
153+
finally
154+
{
155+
// Buffer contains sensitive key material; delete.
156+
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
119157
}
120158
}
159+
}
160+
catch (Exception ex) when (ex.RequiresHomogenization())
161+
{
162+
// Homogenize all exceptions to CryptographicException.
163+
throw Error.CryptCommon_GenericError(ex);
164+
}
165+
}
121166

122-
// If the integrity check succeeded, decrypt the payload.
123-
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
124-
{
125-
return DoCbcDecrypt(decryptionSubkeyHandle, pbIV, pbEncryptedData, cbEncryptedData);
126-
}
167+
public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
168+
{
169+
ciphertext.Validate();
170+
additionalAuthenticatedData.Validate();
171+
172+
var size = GetDecryptedSize(ciphertext.Count);
173+
var plaintext = new byte[size];
174+
var destination = plaintext.AsSpan();
175+
176+
if (!TryDecrypt(
177+
cipherText: ciphertext,
178+
additionalAuthenticatedData: additionalAuthenticatedData,
179+
destination: destination,
180+
out var bytesWritten))
181+
{
182+
throw Error.CryptCommon_GenericError(new ArgumentException("Not enough space in destination array"));
127183
}
128-
finally
184+
185+
// Resize array if needed (due to padding)
186+
if (bytesWritten < size)
129187
{
130-
// Buffer contains sensitive key material; delete.
131-
UnsafeBufferUtil.SecureZeroMemory(pbTempSubkeys, cbTempSubkeys);
188+
var resized = new byte[bytesWritten];
189+
Array.Copy(plaintext, resized, bytesWritten);
190+
return resized;
132191
}
192+
193+
return plaintext;
133194
}
134195

135196
// 'pbIV' must be a pointer to a buffer equal in length to the symmetric algorithm block size.
@@ -145,6 +206,7 @@ private byte[] DoCbcDecrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte
145206
// know the actual padding scheme being used under the covers (we can't
146207
// assume PKCS#7). So unfortunately we're stuck with the temporary buffer.
147208
// (Querying the output size won't mutate the IV.)
209+
148210
uint dwEstimatedDecryptedByteCount;
149211
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
150212
hKey: symmetricKeyHandle,
@@ -369,6 +431,7 @@ private uint GetCbcEncryptedOutputSizeWithPadding(uint cbInput)
369431
byte* pbDummyIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
370432
byte* pbDummyInput = stackalloc byte[checked((int)cbInput)];
371433

434+
372435
var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
373436
hKey: tempKeyHandle,
374437
pbInput: pbDummyInput,

0 commit comments

Comments
 (0)