Skip to content

Commit fe478d2

Browse files
committed
cnggcm
1 parent c029655 commit fe478d2

File tree

2 files changed

+125
-81
lines changed

2 files changed

+125
-81
lines changed

src/DataProtection/DataProtection/src/Cng/CngGcmAuthenticatedEncryptor.cs

Lines changed: 103 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -51,95 +51,123 @@ public CngGcmAuthenticatedEncryptor(Secret keyDerivationKey, BCryptAlgorithmHand
5151
}
5252

5353
public int GetDecryptedSize(int cipherTextLength)
54-
{
55-
throw new NotImplementedException();
56-
}
57-
58-
public bool TryDecrypt(ReadOnlySpan<byte> cipherText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
59-
{
60-
throw new NotImplementedException();
61-
}
62-
63-
public byte[] Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData)
64-
{
65-
throw new NotImplementedException();
66-
}
67-
68-
protected byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData)
6954
{
7055
// Argument checking: input must at the absolute minimum contain a key modifier, nonce, and tag
71-
if (cbCiphertext < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
56+
if (cipherTextLength < KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES)
7257
{
7358
throw Error.CryptCommon_PayloadInvalid();
7459
}
7560

76-
// Assumption: pbCipherText := { keyModifier || nonce || encryptedData || authenticationTag }
61+
return checked(cipherTextLength - (int)(KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES));
62+
}
7763

78-
var cbPlaintext = checked(cbCiphertext - (KEY_MODIFIER_SIZE_IN_BYTES + NONCE_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES));
64+
public bool TryDecrypt(ReadOnlySpan<byte> cipherText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten)
65+
{
66+
bytesWritten = 0;
7967

80-
var retVal = new byte[cbPlaintext];
81-
fixed (byte* pbRetVal = retVal)
68+
try
8269
{
83-
// Calculate offsets
84-
byte* pbKeyModifier = pbCiphertext;
85-
byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
86-
byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
87-
byte* pbAuthTag = &pbEncryptedData[cbPlaintext];
88-
89-
// Use the KDF to recreate the symmetric block cipher key
90-
// We'll need a temporary buffer to hold the symmetric encryption subkey
91-
byte* pbSymmetricDecryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
92-
try
70+
var plaintextLength = GetDecryptedSize(cipherText.Length);
71+
72+
// Check if destination is large enough
73+
if (destination.Length < plaintextLength)
9374
{
94-
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
95-
pbLabel: pbAdditionalAuthenticatedData,
96-
cbLabel: cbAdditionalAuthenticatedData,
97-
contextHeader: _contextHeader,
98-
pbContext: pbKeyModifier,
99-
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
100-
pbDerivedKey: pbSymmetricDecryptionSubkey,
101-
cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
102-
103-
// Perform the decryption operation
104-
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
105-
{
106-
byte dummy;
107-
byte* pbPlaintext = (pbRetVal != null) ? pbRetVal : &dummy; // CLR doesn't like pinning empty buffers
108-
109-
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
110-
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo);
111-
authInfo.pbNonce = pbNonce;
112-
authInfo.cbNonce = NONCE_SIZE_IN_BYTES;
113-
authInfo.pbTag = pbAuthTag;
114-
authInfo.cbTag = TAG_SIZE_IN_BYTES;
115-
116-
// The call to BCryptDecrypt will also validate the authentication tag
117-
uint cbDecryptedBytesWritten;
118-
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
119-
hKey: decryptionSubkeyHandle,
120-
pbInput: pbEncryptedData,
121-
cbInput: cbPlaintext,
122-
pPaddingInfo: &authInfo,
123-
pbIV: null, // IV not used; nonce provided in pPaddingInfo
124-
cbIV: 0,
125-
pbOutput: pbPlaintext,
126-
cbOutput: cbPlaintext,
127-
pcbResult: out cbDecryptedBytesWritten,
128-
dwFlags: 0);
129-
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
130-
CryptoUtil.Assert(cbDecryptedBytesWritten == cbPlaintext, "cbDecryptedBytesWritten == cbPlaintext");
131-
132-
// At this point, retVal := { decryptedPayload }
133-
// And we're done!
134-
return retVal;
135-
}
75+
return false;
13676
}
137-
finally
77+
78+
// Assumption: cipherText := { keyModifier || nonce || encryptedData || authenticationTag }
79+
fixed (byte* pbCiphertext = cipherText)
80+
fixed (byte* pbAdditionalAuthenticatedData = additionalAuthenticatedData)
81+
fixed (byte* pbDestination = destination)
13882
{
139-
// The buffer contains key material, so delete it.
140-
UnsafeBufferUtil.SecureZeroMemory(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
83+
// Calculate offsets
84+
byte* pbKeyModifier = pbCiphertext;
85+
byte* pbNonce = &pbKeyModifier[KEY_MODIFIER_SIZE_IN_BYTES];
86+
byte* pbEncryptedData = &pbNonce[NONCE_SIZE_IN_BYTES];
87+
byte* pbAuthTag = &pbEncryptedData[plaintextLength];
88+
89+
// Use the KDF to recreate the symmetric block cipher key
90+
// We'll need a temporary buffer to hold the symmetric encryption subkey
91+
byte* pbSymmetricDecryptionSubkey = stackalloc byte[checked((int)_symmetricAlgorithmSubkeyLengthInBytes)];
92+
try
93+
{
94+
_sp800_108_ctr_hmac_provider.DeriveKeyWithContextHeader(
95+
pbLabel: pbAdditionalAuthenticatedData,
96+
cbLabel: (uint)additionalAuthenticatedData.Length,
97+
contextHeader: _contextHeader,
98+
pbContext: pbKeyModifier,
99+
cbContext: KEY_MODIFIER_SIZE_IN_BYTES,
100+
pbDerivedKey: pbSymmetricDecryptionSubkey,
101+
cbDerivedKey: _symmetricAlgorithmSubkeyLengthInBytes);
102+
103+
// Perform the decryption operation
104+
using (var decryptionSubkeyHandle = _symmetricAlgorithmHandle.GenerateSymmetricKey(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes))
105+
{
106+
byte dummy;
107+
byte* pbPlaintext = (plaintextLength > 0) ? pbDestination : &dummy; // CLR doesn't like pinning empty buffers
108+
109+
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
110+
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Init(out authInfo);
111+
authInfo.pbNonce = pbNonce;
112+
authInfo.cbNonce = NONCE_SIZE_IN_BYTES;
113+
authInfo.pbTag = pbAuthTag;
114+
authInfo.cbTag = TAG_SIZE_IN_BYTES;
115+
116+
// The call to BCryptDecrypt will also validate the authentication tag
117+
uint cbDecryptedBytesWritten;
118+
var ntstatus = UnsafeNativeMethods.BCryptDecrypt(
119+
hKey: decryptionSubkeyHandle,
120+
pbInput: pbEncryptedData,
121+
cbInput: (uint)plaintextLength,
122+
pPaddingInfo: &authInfo,
123+
pbIV: null, // IV not used; nonce provided in pPaddingInfo
124+
cbIV: 0,
125+
pbOutput: pbPlaintext,
126+
cbOutput: (uint)plaintextLength,
127+
pcbResult: out cbDecryptedBytesWritten,
128+
dwFlags: 0);
129+
UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
130+
CryptoUtil.Assert(cbDecryptedBytesWritten == plaintextLength, "cbDecryptedBytesWritten == plaintextLength");
131+
132+
// At this point, retVal := { decryptedPayload }
133+
// And we're done!
134+
bytesWritten = (int)cbDecryptedBytesWritten;
135+
return true;
136+
}
137+
}
138+
finally
139+
{
140+
// The buffer contains key material, so delete it.
141+
UnsafeBufferUtil.SecureZeroMemory(pbSymmetricDecryptionSubkey, _symmetricAlgorithmSubkeyLengthInBytes);
142+
}
141143
}
142144
}
145+
catch (Exception ex) when (ex.RequiresHomogenization())
146+
{
147+
throw Error.CryptCommon_GenericError(ex);
148+
}
149+
}
150+
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;
143171
}
144172

145173
// 'pbNonce' must point to a 96-bit buffer.

src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/Internal/RoundtripEncryptionHelpers.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,40 @@ public static void AssertTryEncryptTryDecryptParity(IAuthenticatedEncryptor encr
3737
byte[] decipheredtext = encryptor.Decrypt(new ArraySegment<byte>(ciphertext), aad);
3838
Assert.Equal(plaintext.AsSpan(), decipheredtext.AsSpan());
3939

40-
// assert calculated size is correct
41-
var expectedSize = spanAuthenticatedEncryptor.GetEncryptedSize(plaintext.Count);
42-
Assert.Equal(expectedSize, ciphertext.Length);
40+
// assert calculated sizes are correct
41+
var expectedEncryptedSize = spanAuthenticatedEncryptor.GetEncryptedSize(plaintext.Count);
42+
Assert.Equal(expectedEncryptedSize, ciphertext.Length);
43+
var expectedDecryptedSize = spanAuthenticatedEncryptor.GetDecryptedSize(ciphertext.Length);
44+
Assert.Equal(expectedDecryptedSize, decipheredtext.Length);
4345

4446
// perform TryEncrypt and Decrypt roundtrip - ensures cross operation compatibility
45-
var cipherTextPooled = ArrayPool<byte>.Shared.Rent(expectedSize);
47+
var cipherTextPooled = ArrayPool<byte>.Shared.Rent(expectedEncryptedSize);
4648
try
4749
{
4850
var tryEncryptResult = spanAuthenticatedEncryptor.TryEncrypt(plaintext, aad, cipherTextPooled, out var bytesWritten);
49-
Assert.Equal(expectedSize, bytesWritten);
51+
Assert.Equal(expectedEncryptedSize, bytesWritten);
5052
Assert.True(tryEncryptResult);
5153

52-
var decipheredTryEncrypt = encryptor.Decrypt(new ArraySegment<byte>(cipherTextPooled, 0, expectedSize), aad);
54+
var decipheredTryEncrypt = spanAuthenticatedEncryptor.Decrypt(new ArraySegment<byte>(cipherTextPooled, 0, expectedEncryptedSize), aad);
5355
Assert.Equal(plaintext.AsSpan(), decipheredTryEncrypt.AsSpan());
5456
}
5557
finally
5658
{
5759
ArrayPool<byte>.Shared.Return(cipherTextPooled);
5860
}
61+
62+
// perform Encrypt and TryDecrypt roundtrip - ensures cross operation compatibility
63+
var plainTextPooled = ArrayPool<byte>.Shared.Rent(expectedDecryptedSize);
64+
try
65+
{
66+
var encrypted = spanAuthenticatedEncryptor.Encrypt(plaintext, aad);
67+
var decipheredTryDecrypt = spanAuthenticatedEncryptor.TryDecrypt(encrypted, aad, plainTextPooled, out var bytesWritten);
68+
Assert.Equal(plaintext.AsSpan(), plainTextPooled.AsSpan());
69+
Assert.True(decipheredTryDecrypt);
70+
}
71+
finally
72+
{
73+
ArrayPool<byte>.Shared.Return(cipherTextPooled);
74+
}
5975
}
6076
}

0 commit comments

Comments
 (0)