@@ -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