@@ -64,73 +64,80 @@ public AesGcmAuthenticatedEncryptor(ISecret keyDerivationKey, int derivedKeySize
6464 _genRandom = genRandom ?? ManagedGenRandomImpl . Instance ;
6565 }
6666
67- public byte [ ] Decrypt ( ArraySegment < byte > ciphertext , ArraySegment < byte > additionalAuthenticatedData )
67+ public int GetDecryptedSize ( int cipherTextLength )
6868 {
69- ciphertext . Validate ( ) ;
70- additionalAuthenticatedData . Validate ( ) ;
71-
7269 // Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
73- if ( ciphertext . Count < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES )
70+ if ( cipherTextLength < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES )
7471 {
7572 throw Error . CryptCommon_PayloadInvalid ( ) ;
7673 }
7774
78- // Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag }
79- var plaintextBytes = ciphertext . Count - ( KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES ) ;
80- var plaintext = new byte [ plaintextBytes ] ;
75+ return cipherTextLength - ( KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES ) ;
76+ }
77+
78+ public bool TryDecrypt ( ReadOnlySpan < byte > cipherText , ReadOnlySpan < byte > additionalAuthenticatedData , Span < byte > destination , out int bytesWritten )
79+ {
80+ bytesWritten = 0 ;
8181
8282 try
8383 {
84- // Step 1: Extract the key modifier from the payload.
85-
86- int keyModifierOffset ; // position in ciphertext.Array where key modifier begins
87- int nonceOffset ; // position in ciphertext.Array where key modifier ends / nonce begins
88- int encryptedDataOffset ; // position in ciphertext.Array where nonce ends / encryptedData begins
89- int tagOffset ; // position in ciphertext.Array where encrypted data ends
90-
91- checked
84+ var plaintextBytes = GetDecryptedSize ( cipherText . Length ) ;
85+ if ( destination . Length < plaintextBytes )
9286 {
93- keyModifierOffset = ciphertext . Offset ;
94- nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES ;
95- encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES ;
96- tagOffset = encryptedDataOffset + plaintextBytes ;
87+ return false ;
9788 }
9889
99- var keyModifier = new ArraySegment < byte > ( ciphertext . Array ! , keyModifierOffset , KEY_MODIFIER_SIZE_IN_BYTES ) ;
90+ // Calculate offsets in the cipherText
91+ var keyModifierOffset = 0 ;
92+ var nonceOffset = keyModifierOffset + KEY_MODIFIER_SIZE_IN_BYTES ;
93+ var encryptedDataOffset = nonceOffset + NONCE_SIZE_IN_BYTES ;
94+ var tagOffset = encryptedDataOffset + plaintextBytes ;
10095
101- // Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
102- // We pin all unencrypted keys to limit their exposure via GC relocation.
96+ // Extract spans for each component
97+ var keyModifier = cipherText . Slice ( keyModifierOffset , KEY_MODIFIER_SIZE_IN_BYTES ) ;
98+ var nonce = cipherText . Slice ( nonceOffset , NONCE_SIZE_IN_BYTES ) ;
99+ var encrypted = cipherText . Slice ( encryptedDataOffset , plaintextBytes ) ;
100+ var tag = cipherText . Slice ( tagOffset , TAG_SIZE_IN_BYTES ) ;
103101
104- var decryptedKdk = new byte [ _keyDerivationKey . Length ] ;
105- var derivedKey = new byte [ _derivedkeySizeInBytes ] ;
102+ // Get the plaintext destination
103+ var plaintext = destination . Slice ( 0 , plaintextBytes ) ;
106104
107- fixed ( byte * __unused__1 = decryptedKdk )
108- fixed ( byte * __unused__2 = derivedKey )
105+ // Decrypt the KDK and use it to restore the original encryption key
106+ // We pin all unencrypted keys to limit their exposure via GC relocation
107+ Span < byte > decryptedKdk = _keyDerivationKey . Length <= 256
108+ ? stackalloc byte [ 256 ] . Slice ( 0 , _keyDerivationKey . Length )
109+ : new byte [ _keyDerivationKey . Length ] ;
110+
111+ Span < byte > derivedKey = _derivedkeySizeInBytes <= 256
112+ ? stackalloc byte [ 256 ] . Slice ( 0 , _derivedkeySizeInBytes )
113+ : new byte [ _derivedkeySizeInBytes ] ;
114+
115+ fixed ( byte * decryptedKdkUnsafe = decryptedKdk )
116+ fixed ( byte * derivedKeyUnsafe = derivedKey )
109117 {
110118 try
111119 {
112- _keyDerivationKey . WriteSecretIntoBuffer ( new ArraySegment < byte > ( decryptedKdk ) ) ;
120+ _keyDerivationKey . WriteSecretIntoBuffer ( decryptedKdkUnsafe , decryptedKdk . Length ) ;
113121 ManagedSP800_108_CTR_HMACSHA512 . DeriveKeys (
114122 kdk : decryptedKdk ,
115123 label : additionalAuthenticatedData ,
116124 contextHeader : _contextHeader ,
117125 contextData : keyModifier ,
118126 operationSubkey : derivedKey ,
119- validationSubkey : Span < byte > . Empty /* filling in derivedKey only */ ) ;
127+ validationSubkey : Span < byte > . Empty /* filling in derivedKey only */ ) ;
120128
121- // Perform the decryption operation
122- var nonce = new Span < byte > ( ciphertext . Array , nonceOffset , NONCE_SIZE_IN_BYTES ) ;
123- var tag = new Span < byte > ( ciphertext . Array , tagOffset , TAG_SIZE_IN_BYTES ) ;
124- var encrypted = new Span < byte > ( ciphertext . Array , encryptedDataOffset , plaintextBytes ) ;
129+ // Perform the decryption operation directly into destination
125130 using var aes = new AesGcm ( derivedKey , TAG_SIZE_IN_BYTES ) ;
126131 aes . Decrypt ( nonce , encrypted , tag , plaintext ) ;
127- return plaintext ;
132+
133+ bytesWritten = plaintextBytes ;
134+ return true ;
128135 }
129136 finally
130137 {
131138 // delete since these contain secret material
132- Array . Clear ( decryptedKdk , 0 , decryptedKdk . Length ) ;
133- Array . Clear ( derivedKey , 0 , derivedKey . Length ) ;
139+ decryptedKdk . Clear ( ) ;
140+ derivedKey . Clear ( ) ;
134141 }
135142 }
136143 }
@@ -141,6 +148,28 @@ public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> addition
141148 }
142149 }
143150
151+ public byte [ ] Decrypt ( ArraySegment < byte > ciphertext , ArraySegment < byte > additionalAuthenticatedData )
152+ {
153+ ciphertext . Validate ( ) ;
154+ additionalAuthenticatedData . Validate ( ) ;
155+
156+ var size = GetDecryptedSize ( ciphertext . Count ) ;
157+ var plaintext = new byte [ size ] ;
158+ var destination = plaintext . AsSpan ( ) ;
159+
160+ if ( ! TryDecrypt (
161+ cipherText : ciphertext ,
162+ additionalAuthenticatedData : additionalAuthenticatedData ,
163+ destination : destination ,
164+ out var bytesWritten ) )
165+ {
166+ throw Error . CryptCommon_GenericError ( new ArgumentException ( "Not enough space in destination array" ) ) ;
167+ }
168+
169+ CryptoUtil . Assert ( bytesWritten == size , "bytesWritten == size" ) ;
170+ return plaintext ;
171+ }
172+
144173 public byte [ ] Encrypt ( ArraySegment < byte > plaintext , ArraySegment < byte > additionalAuthenticatedData , uint preBufferSize , uint postBufferSize )
145174 {
146175 plaintext . Validate ( ) ;
0 commit comments