Skip to content

Commit 21b7d87

Browse files
committed
AesGcm
1 parent 516729c commit 21b7d87

File tree

1 file changed

+65
-36
lines changed

1 file changed

+65
-36
lines changed

src/DataProtection/DataProtection/src/Managed/AesGcmAuthenticatedEncryptor.cs

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)