Skip to content

Commit 11a249f

Browse files
committed
improve CBC authenticated encryptor
1 parent 4d2a8b4 commit 11a249f

File tree

1 file changed

+31
-47
lines changed

1 file changed

+31
-47
lines changed

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

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public CbcAuthenticatedEncryptor(Secret keyDerivationKey, BCryptAlgorithmHandle
5959

6060
public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> additionalAuthenticatedData, ref TWriter destination) where TWriter : IBufferWriter<byte>
6161
#if NET
62-
, allows ref struct
62+
, allows ref struct
6363
#endif
6464
{
6565
// Argument checking - input must at the absolute minimum contain a key modifier, IV, and MAC
@@ -113,43 +113,28 @@ public void Decrypt<TWriter>(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> a
113113
// If the integrity check succeeded, decrypt the payload.
114114
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
115115
{
116-
// BCryptDecrypt mutates the provided IV; we need to clone it to prevent mutation of the original value
117-
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
118-
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
119-
120-
// First, query the output size needed
121-
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
122-
hKey: decryptionSubkeyHandle,
123-
pbInput: pbEncryptedData,
124-
cbInput: (uint)cbEncryptedDataLength,
125-
pPaddingInfo: null,
126-
pbIV: pbClonedIV,
127-
cbIV: _symmetricAlgorithmBlockSizeInBytes,
128-
pbOutput: null, // NULL output = size query only
129-
cbOutput: 0,
130-
pcbResult: out var dwRequiredSize,
131-
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
132-
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
133-
134-
// Get buffer from writer with the required size
135-
var buffer = destination.GetSpan(checked((int)dwRequiredSize));
136-
137-
// Clone IV again for the actual decryption call
138-
byte* pbClonedIV2 = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
139-
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV2, byteCount: _symmetricAlgorithmBlockSizeInBytes);
140-
141-
// Perform the actual decryption
116+
// For CBC mode with PKCS7 padding, the maximum plaintext size is equal to the ciphertext size
117+
// We allocate the maximum possible size and let BCryptDecrypt tell us the actual size
118+
var buffer = destination.GetSpan(cbEncryptedDataLength);
119+
142120
fixed (byte* pbBuffer = buffer)
143121
{
144122
byte dummy;
145-
ntstatus = UnsafeNativeMethods.BCryptDecrypt(
123+
byte* pbPlaintext = (buffer.Length > 0) ? pbBuffer : &dummy;
124+
125+
// BCryptDecrypt mutates the provided IV; we need to clone it
126+
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
127+
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
128+
129+
// Single BCryptDecrypt call (no size query needed)
130+
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
146131
hKey: decryptionSubkeyHandle,
147132
pbInput: pbEncryptedData,
148133
cbInput: (uint)cbEncryptedDataLength,
149134
pPaddingInfo: null,
150-
pbIV: pbClonedIV2,
135+
pbIV: pbClonedIV,
151136
cbIV: _symmetricAlgorithmBlockSizeInBytes,
152-
pbOutput: (buffer.Length > 0) ? pbBuffer : &dummy,
137+
pbOutput: pbPlaintext,
153138
cbOutput: (uint)buffer.Length,
154139
pcbResult: out var dwActualDecryptedByteCount,
155140
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
@@ -240,9 +225,20 @@ private void DoCbcEncrypt(BCryptKeyHandle symmetricKeyHandle, byte* pbIV, byte*
240225
CryptoUtil.Assert(dwEncryptedBytes == cbOutput, "dwEncryptedBytes == cbOutput");
241226
}
242227

228+
/// <summary>
229+
/// Calculates CBC-padded ciphertext size deterministically without BCryptEncrypt calls.
230+
/// For CBC mode with PKCS7 padding: paddedSize = RoundUpToBlockSize(plaintextLength)
231+
/// </summary>
232+
private uint CalculateCbcPaddedSize(uint cbPlaintext)
233+
{
234+
// PKCS7 padding: round up to the next multiple of block size
235+
// This is equivalent to: ((plaintextLength + blockSize - 1) / blockSize) * blockSize
236+
return checked((uint)(((cbPlaintext + _symmetricAlgorithmBlockSizeInBytes - 1) / _symmetricAlgorithmBlockSizeInBytes) * _symmetricAlgorithmBlockSizeInBytes));
237+
}
238+
243239
public int GetEncryptedSize(int plainTextLength)
244240
{
245-
uint paddedCiphertextLength = GetCbcEncryptedOutputSizeWithPadding((uint)plainTextLength);
241+
uint paddedCiphertextLength = CalculateCbcPaddedSize((uint)plainTextLength);
246242
return checked((int)(KEY_MODIFIER_SIZE_IN_BYTES + _symmetricAlgorithmBlockSizeInBytes + paddedCiphertextLength + _hmacAlgorithmDigestLengthInBytes));
247243
}
248244

@@ -286,25 +282,13 @@ public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> ad
286282

287283
using (var symmetricKeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricEncryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
288284
{
289-
// Query the padded ciphertext output size
290285
byte dummy;
291286
fixed (byte* pbPlaintextArray = plaintext)
292287
{
293288
var pbPlaintext = (pbPlaintextArray != null) ? pbPlaintextArray : &dummy;
294289

295-
// First, query the size needed for ciphertext
296-
var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
297-
hKey: symmetricKeyHandle,
298-
pbInput: pbPlaintext,
299-
cbInput: (uint)plaintext.Length,
300-
pPaddingInfo: null,
301-
pbIV: pbIV,
302-
cbIV: _symmetricAlgorithmBlockSizeInBytes,
303-
pbOutput: null, // NULL output = size query only
304-
cbOutput: 0,
305-
pcbResult: out var dwCiphertextSize,
306-
dwFlags: BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
307-
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
290+
// Calculate the padded ciphertext size deterministically without BCryptEncrypt
291+
uint dwCiphertextSize = CalculateCbcPaddedSize((uint)plaintext.Length);
308292

309293
// Calculate total required size
310294
var totalRequiredSize = checked((int)(cbKeyModifierAndIV + dwCiphertextSize + _hmacAlgorithmDigestLengthInBytes));
@@ -327,8 +311,8 @@ public void Encrypt<TWriter>(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> ad
327311
byte* pbClonedIV = stackalloc byte[checked((int)_symmetricAlgorithmBlockSizeInBytes)];
328312
UnsafeBufferUtil.BlockCopy(from: pbIV, to: pbClonedIV, byteCount: _symmetricAlgorithmBlockSizeInBytes);
329313

330-
// Perform encryption
331-
ntstatus = UnsafeNativeMethods.BCryptEncrypt(
314+
// Single BCryptEncrypt call (no size query needed)
315+
var ntstatus = UnsafeNativeMethods.BCryptEncrypt(
332316
hKey: symmetricKeyHandle,
333317
pbInput: pbPlaintext,
334318
cbInput: (uint)plaintext.Length,

0 commit comments

Comments
 (0)