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