From e06ac16c6318121aefb746147251ae3fec96db33 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:13:46 +0100
Subject: [PATCH 01/27] Rename SqlSymmetricKeyCache, move to AlwaysEncrypted
namespace.
---
.../{ => AlwaysEncrypted}/SqlSymmetricKeyCache.cs | 10 +++++-----
.../Microsoft/Data/SqlClient/SqlSecurityUtility.cs | 4 ++--
.../TestFixtures/Setup/CertificateUtility.cs | 12 ++++++------
3 files changed, 13 insertions(+), 13 deletions(-)
rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{ => AlwaysEncrypted}/SqlSymmetricKeyCache.cs (95%)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
similarity index 95%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index d88833abed..3e23334e0a 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -8,23 +8,23 @@
using System.Threading;
using Microsoft.Extensions.Caching.Memory;
-namespace Microsoft.Data.SqlClient
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
/// Implements a cache of Symmetric Keys (once they are decrypted).Useful for rapidly decrypting multiple data values.
///
- sealed internal class SqlSymmetricKeyCache
+ sealed internal class SymmetricKeyCache
{
private readonly MemoryCache _cache;
- private static readonly SqlSymmetricKeyCache _singletonInstance = new();
+ private static readonly SymmetricKeyCache _singletonInstance = new();
private static SemaphoreSlim _cacheLock = new(1, 1);
- private SqlSymmetricKeyCache()
+ private SymmetricKeyCache()
{
_cache = new MemoryCache(new MemoryCacheOptions());
}
- internal static SqlSymmetricKeyCache GetInstance()
+ internal static SymmetricKeyCache GetInstance()
{
return _singletonInstance;
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
index 253ab92db8..573cb67bf1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -227,7 +227,7 @@ internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoE
sqlClientSymmetricKey = null;
encryptionkeyInfoChosen = null;
Exception lastException = null;
- SqlSymmetricKeyCache globalCekCache = SqlSymmetricKeyCache.GetInstance();
+ SymmetricKeyCache globalCekCache = SymmetricKeyCache.GetInstance();
foreach (SqlEncryptionKeyInfo keyInfo in sqlTceCipherInfoEntry.ColumnEncryptionKeyValues)
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
index 8232fe56e0..e1a451384f 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -27,17 +27,17 @@ private CertificateUtility()
/// System.Data assembly.
///
public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection));
- public static Type SqlSymmetricKeyCache = systemData.GetType("Microsoft.Data.SqlClient.SqlSymmetricKeyCache");
- public static MethodInfo SqlSymmetricKeyCacheGetInstance = SqlSymmetricKeyCache.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.NonPublic);
- public static FieldInfo SqlSymmetricKeyCacheFieldCache = SqlSymmetricKeyCache.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic);
+ public static Type SymmetricKeyCache = systemData.GetType("Microsoft.Data.SqlClient.AlwaysEncrypted.SymmetricKeyCache");
+ public static MethodInfo SymmetricKeyCacheGetInstance = SymmetricKeyCache.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.NonPublic);
+ public static FieldInfo SymmetricKeyCacheFieldCache = SymmetricKeyCache.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic);
///
/// Through reflection, clear the SqlClient cache
///
internal static void CleanSqlClientCache()
{
- object sqlSymmetricKeyCache = SqlSymmetricKeyCacheGetInstance.Invoke(null, null);
- MemoryCache cache = SqlSymmetricKeyCacheFieldCache.GetValue(sqlSymmetricKeyCache) as MemoryCache;
+ object sqlSymmetricKeyCache = SymmetricKeyCacheGetInstance.Invoke(null, null);
+ MemoryCache cache = SymmetricKeyCacheFieldCache.GetValue(sqlSymmetricKeyCache) as MemoryCache;
ClearCache(cache);
}
From 1c62247a9fa69929efcae83524fa7a07b4e84cf9 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:15:50 +0100
Subject: [PATCH 02/27] Enable nullability annotations.
---
.../SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index 3e23334e0a..bfd653f08c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -8,6 +8,8 @@
using System.Threading;
using Microsoft.Extensions.Caching.Memory;
+#nullable enable
+
namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
@@ -32,11 +34,11 @@ internal static SymmetricKeyCache GetInstance()
///
/// Retrieves Symmetric Key (in plaintext) given the encryption material.
///
- internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command)
+ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand? command)
{
string serverName = connection.DataSource;
Debug.Assert(serverName is not null, @"serverName should not be null.");
- StringBuilder cacheLookupKeyBuilder = new(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
+ StringBuilder cacheLookupKeyBuilder = new(serverName, capacity: serverName!.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
#if DEBUG
int capacity = cacheLookupKeyBuilder.Capacity;
@@ -59,7 +61,9 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
try
{
// Lookup the key in cache
- if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey encryptionKey)))
+ if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey? encryptionKey))
+ // A null cryptographic key is never added to the cache, but this null check satisfies the nullability warning.
+ || encryptionKey is null)
{
Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
From 2620b05c711041ac341261e27906579196d606df Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:18:22 +0100
Subject: [PATCH 03/27] Align singleton to codebase convention.
---
.../Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs | 7 ++-----
.../src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs | 2 +-
.../TestFixtures/Setup/CertificateUtility.cs | 4 ++--
3 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index bfd653f08c..96e520e7b3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -18,7 +18,6 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
sealed internal class SymmetricKeyCache
{
private readonly MemoryCache _cache;
- private static readonly SymmetricKeyCache _singletonInstance = new();
private static SemaphoreSlim _cacheLock = new(1, 1);
private SymmetricKeyCache()
@@ -26,10 +25,8 @@ private SymmetricKeyCache()
_cache = new MemoryCache(new MemoryCacheOptions());
}
- internal static SymmetricKeyCache GetInstance()
- {
- return _singletonInstance;
- }
+ public static SymmetricKeyCache Instance =>
+ field ??= new();
///
/// Retrieves Symmetric Key (in plaintext) given the encryption material.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
index 573cb67bf1..a53d8983da 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
@@ -227,7 +227,7 @@ internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoE
sqlClientSymmetricKey = null;
encryptionkeyInfoChosen = null;
Exception lastException = null;
- SymmetricKeyCache globalCekCache = SymmetricKeyCache.GetInstance();
+ SymmetricKeyCache globalCekCache = SymmetricKeyCache.Instance;
foreach (SqlEncryptionKeyInfo keyInfo in sqlTceCipherInfoEntry.ColumnEncryptionKeyValues)
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
index e1a451384f..c2f2304b9b 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs
@@ -28,7 +28,7 @@ private CertificateUtility()
///
public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection));
public static Type SymmetricKeyCache = systemData.GetType("Microsoft.Data.SqlClient.AlwaysEncrypted.SymmetricKeyCache");
- public static MethodInfo SymmetricKeyCacheGetInstance = SymmetricKeyCache.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.NonPublic);
+ public static PropertyInfo SymmetricKeyCacheInstance = SymmetricKeyCache.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
public static FieldInfo SymmetricKeyCacheFieldCache = SymmetricKeyCache.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic);
///
@@ -36,7 +36,7 @@ private CertificateUtility()
///
internal static void CleanSqlClientCache()
{
- object sqlSymmetricKeyCache = SymmetricKeyCacheGetInstance.Invoke(null, null);
+ object sqlSymmetricKeyCache = SymmetricKeyCacheInstance.GetValue(null);
MemoryCache cache = SymmetricKeyCacheFieldCache.GetValue(sqlSymmetricKeyCache) as MemoryCache;
ClearCache(cache);
}
From 6606c06bc6a45fc03b886cce47b90bf8a3914586 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:19:01 +0100
Subject: [PATCH 04/27] Eliminate construction of new TimeSpan(0).
---
.../Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index 96e520e7b3..aec9ac0394 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -82,7 +82,7 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
// AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider.
// The CEK cache is a global cache, and is shared across all connections.
// To prevent conflicts between CEK caches, global providers should not use their own CEK caches
- provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0);
+ provider.ColumnEncryptionKeyCacheTtl = TimeSpan.Zero;
plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
}
catch (Exception e)
From f5959d576860617952c29144d6abd8714c401627 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:21:19 +0100
Subject: [PATCH 05/27] Reduce lock contention.
The underlying MemoryCache is thread-safe at the point of retrieval. Add a check outside of the lock to avoid contending it where possible.
---
.../AlwaysEncrypted/SqlSymmetricKeyCache.cs | 109 +++++++++---------
1 file changed, 57 insertions(+), 52 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index aec9ac0394..0bd8982796 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -52,64 +52,69 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array");
#endif //DEBUG
- // Acquire the lock to ensure thread safety when accessing the cache
- _cacheLock.Wait();
-
- try
+ // Lookup the key in cache
+ if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey? encryptionKey))
+ // A null cryptographic key is never added to the cache, but this null check satisfies the nullability warning.
+ || encryptionKey is null)
{
- // Lookup the key in cache
- if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey? encryptionKey))
- // A null cryptographic key is never added to the cache, but this null check satisfies the nullability warning.
- || encryptionKey is null)
- {
- Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
-
- SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath);
-
- // Key Not found, attempt to look up the provider and decrypt CEK
- if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command))
- {
- throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
- SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(),
- SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command));
- }
+ // Acquire the lock to ensure thread safety when modifying the cache
+ _cacheLock.Wait();
- // Decrypt the CEK
- // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
- byte[] plaintextKey;
- try
- {
- // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider.
- // The CEK cache is a global cache, and is shared across all connections.
- // To prevent conflicts between CEK caches, global providers should not use their own CEK caches
- provider.ColumnEncryptionKeyCacheTtl = TimeSpan.Zero;
- plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
- }
- catch (Exception e)
- {
- // Generate a new exception and throw.
- string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10);
- throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
- }
-
- encryptionKey = new SqlClientSymmetricKey(plaintextKey);
-
- // If the cache TTL is zero, don't even bother inserting to the cache.
- if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
+ try
+ {
+ // Perform a second check to see if the key was added to the cache while waiting for the lock, to avoid redundant work.
+ if (!(_cache.TryGetValue(cacheLookupKey, out encryptionKey))
+ || encryptionKey is null)
{
- // In case multiple threads reach here at the same time, the first one wins.
- // The allocated memory will be reclaimed by Garbage Collector.
- _cache.Set(cacheLookupKey, encryptionKey, absoluteExpirationRelativeToNow: SqlConnection.ColumnEncryptionKeyCacheTtl);
+ Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
+
+ SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath);
+
+ // Key Not found, attempt to look up the provider and decrypt CEK
+ if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command))
+ {
+ throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
+ SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(),
+ SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command));
+ }
+
+ // Decrypt the CEK
+ // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
+ byte[] plaintextKey;
+ try
+ {
+ // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider.
+ // The CEK cache is a global cache, and is shared across all connections.
+ // To prevent conflicts between CEK caches, global providers should not use their own CEK caches
+ provider.ColumnEncryptionKeyCacheTtl = TimeSpan.Zero;
+ plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
+ }
+ catch (Exception e)
+ {
+ // Generate a new exception and throw.
+ string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10);
+ throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
+ }
+
+ encryptionKey = new SqlClientSymmetricKey(plaintextKey);
+
+ // If the cache TTL is zero, don't even bother inserting to the cache.
+ if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
+ {
+ // In case multiple threads reach here at the same time, the first one wins.
+ // The allocated memory will be reclaimed by Garbage Collector.
+ _cache.Set(cacheLookupKey, encryptionKey, absoluteExpirationRelativeToNow: SqlConnection.ColumnEncryptionKeyCacheTtl);
+ }
}
}
-
- return encryptionKey;
- }
- finally
- {
- // Release the lock to allow other threads to access the cache
- _cacheLock.Release();
+ finally
+ {
+ // Release the lock to allow other threads to access the cache
+ _cacheLock.Release();
+ }
}
+
+ return encryptionKey;
}
}
}
From 59bf3d91c87de8551a0e6f291429c890abad9af7 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:25:54 +0100
Subject: [PATCH 06/27] Style cleanup.
---
.../AlwaysEncrypted/SqlSymmetricKeyCache.cs | 30 ++++++++-----------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
index 0bd8982796..ad86d4817a 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
@@ -13,12 +13,12 @@
namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
- /// Implements a cache of Symmetric Keys (once they are decrypted).Useful for rapidly decrypting multiple data values.
+ /// Implements a cache of Symmetric Keys (once they are decrypted). Useful for rapidly decrypting multiple data values.
///
- sealed internal class SymmetricKeyCache
+ internal sealed class SymmetricKeyCache
{
private readonly MemoryCache _cache;
- private static SemaphoreSlim _cacheLock = new(1, 1);
+ private static readonly SemaphoreSlim s_cacheLock = new(1, 1);
private SymmetricKeyCache()
{
@@ -29,36 +29,32 @@ private SymmetricKeyCache()
field ??= new();
///
- /// Retrieves Symmetric Key (in plaintext) given the encryption material.
+ /// Retrieves Symmetric Key (in plaintext) given the encryption material.
///
- internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand? command)
+ public SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand? command)
{
string serverName = connection.DataSource;
Debug.Assert(serverName is not null, @"serverName should not be null.");
- StringBuilder cacheLookupKeyBuilder = new(serverName, capacity: serverName!.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
+ int capacity = serverName!.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2 /* separators */;
+ StringBuilder cacheLookupKeyBuilder = new(serverName, capacity);
-#if DEBUG
- int capacity = cacheLookupKeyBuilder.Capacity;
-#endif //DEBUG
-
- cacheLookupKeyBuilder.Append(":");
+ cacheLookupKeyBuilder.Append(':');
cacheLookupKeyBuilder.Append(Convert.ToBase64String(keyInfo.encryptedKey));
- cacheLookupKeyBuilder.Append(":");
+ cacheLookupKeyBuilder.Append(':');
cacheLookupKeyBuilder.Append(keyInfo.keyStoreName);
string cacheLookupKey = cacheLookupKeyBuilder.ToString();
-#if DEBUG
Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array");
-#endif //DEBUG
// Lookup the key in cache
if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey? encryptionKey))
// A null cryptographic key is never added to the cache, but this null check satisfies the nullability warning.
|| encryptionKey is null)
{
- // Acquire the lock to ensure thread safety when modifying the cache
- _cacheLock.Wait();
+ // Acquire the lock to ensure thread safety when modifying the cache, and to guarantee that only one thread calls
+ // DecryptColumnEncryptionKey on a user-provided SqlColumnEncryptionKeyStoreProvider at a time.
+ s_cacheLock.Wait();
try
{
@@ -110,7 +106,7 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio
finally
{
// Release the lock to allow other threads to access the cache
- _cacheLock.Release();
+ s_cacheLock.Release();
}
}
From 521fcd9593e07308c41b5e0ac82b779d25b14179 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Mon, 27 Apr 2026 06:26:39 +0100
Subject: [PATCH 07/27] Rename SqlSymmetricKeyCache to match new class name.
---
.../{SqlSymmetricKeyCache.cs => SymmetricKeyCache.cs} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/{SqlSymmetricKeyCache.cs => SymmetricKeyCache.cs} (100%)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
similarity index 100%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlSymmetricKeyCache.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
From 615f4c7c250b36a1eade693c85150517846c451b Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 05:45:42 +0100
Subject: [PATCH 08/27] Rename and move SqlClientSymmetricKey
---
.../AeadAes256CbcHmac256Factory.cs | 2 +-
.../EncryptionAlgorithmFactory.cs | 2 +-
.../EncryptionAlgorithmFactoryList.cs | 2 +-
.../SymmetricKey.cs} | 8 ++++----
.../AlwaysEncrypted/SymmetricKeyCache.cs | 6 +++---
.../Microsoft/Data/SqlClient/EnclaveDelegate.cs | 4 ++--
.../SqlAeadAes256CbcHmac256EncryptionKey.cs | 17 +++++++++--------
.../Data/SqlClient/SqlSecurityUtility.cs | 8 ++++----
.../ExceptionsAlgorithmErrors.cs | 4 ++--
.../AlwaysEncryptedTests/Utility.cs | 4 ++--
.../AlwaysEncrypted/NativeAeadBaseline.cs | 4 ++--
11 files changed, 31 insertions(+), 30 deletions(-)
rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlClientSymmetricKey.cs => AlwaysEncrypted/SymmetricKey.cs} (86%)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
index 2a8ebcd6b0..ab6e7259f1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
@@ -41,7 +41,7 @@ private AeadAes256CbcHmac256Factory() { }
/// Encryption type. Expected values are either Deterministic or Randomized.
/// Cryptographic algorithm.
/// An implementation of the AEAD_AES_256_CBC_HMAC_SHA256 cryptographic algorithm.
- internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm)
+ internal override SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm)
{
// Callers should have validated the encryption algorithm and the encryption key
Debug.Assert(string.Equals(encryptionAlgorithm, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase));
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
index 545a581883..a56db20dce 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
@@ -20,5 +20,5 @@ internal abstract class EncryptionAlgorithmFactory
/// Encryption Type, some algorithms will need this
/// Cryptographic algorithm name. Needed for extracting version bits
/// Return a newly created SqlClientEncryptionAlgorithm instance
- internal abstract SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm);
+ internal abstract SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
index 0b28dd9ef1..71a46523c9 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
@@ -24,7 +24,7 @@ internal static class EncryptionAlgorithmFactoryList
/// Encryption type (read from SQL Server.)
/// Name of the cryptographic algorithm.
/// Specified cryptographic algorithm's implementation.
- public static void GetAlgorithm(SqlClientSymmetricKey key, byte type, string algorithmName, out SqlClientEncryptionAlgorithm encryptionAlgorithm)
+ public static void GetAlgorithm(SymmetricKey key, byte type, string algorithmName, out SqlClientEncryptionAlgorithm encryptionAlgorithm)
{
EncryptionAlgorithmFactory factory = algorithmName switch
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientSymmetricKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
similarity index 86%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientSymmetricKey.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
index a325073637..ebbc033e81 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientSymmetricKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
@@ -1,14 +1,14 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-namespace Microsoft.Data.SqlClient
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
/// Base class containing raw key bytes for symmetric key algorithms. Some encryption algorithms can use the key directly while others derive sub keys from this.
/// If an algorithm needs to derive more keys, have a derived class from this and use it in the corresponding encryption algorithm.
///
- internal class SqlClientSymmetricKey
+ internal class SymmetricKey
{
///
/// The underlying key material
@@ -19,7 +19,7 @@ internal class SqlClientSymmetricKey
/// Constructor that initializes the root key.
///
/// root key
- internal SqlClientSymmetricKey(byte[] rootKey)
+ internal SymmetricKey(byte[] rootKey)
{
// Key validation
if (rootKey == null || rootKey.Length == 0)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
index ad86d4817a..c21dc0863c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
@@ -31,7 +31,7 @@ private SymmetricKeyCache()
///
/// Retrieves Symmetric Key (in plaintext) given the encryption material.
///
- public SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand? command)
+ public SymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand? command)
{
string serverName = connection.DataSource;
Debug.Assert(serverName is not null, @"serverName should not be null.");
@@ -48,7 +48,7 @@ public SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection
Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array");
// Lookup the key in cache
- if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey? encryptionKey))
+ if (!(_cache.TryGetValue(cacheLookupKey, out SymmetricKey? encryptionKey))
// A null cryptographic key is never added to the cache, but this null check satisfies the nullability warning.
|| encryptionKey is null)
{
@@ -92,7 +92,7 @@ public SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection
throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
}
- encryptionKey = new SqlClientSymmetricKey(plaintextKey);
+ encryptionKey = new SymmetricKey(plaintextKey);
// If the cache TTL is zero, don't even bother inserting to the cache.
if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
index dcd5fd200a..4b3ace729f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
@@ -56,7 +56,7 @@ private List GetDecryptedKeysToBeSentToEnclave(Concurre
foreach (SqlTceCipherInfoEntry cipherInfo in keysTobeSentToEnclave.Values)
{
- SqlSecurityUtility.DecryptSymmetricKey(cipherInfo, out SqlClientSymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo encryptionkeyInfoChosen, connection, command);
+ SqlSecurityUtility.DecryptSymmetricKey(cipherInfo, out SymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo encryptionkeyInfoChosen, connection, command);
if (sqlClientSymmetricKey == null)
{
@@ -151,7 +151,7 @@ private byte[] EncryptBytePackage(byte[] bytePackage, byte[] sessionKey, string
try
{
- SqlClientSymmetricKey symmetricKey = new SqlClientSymmetricKey(sessionKey);
+ SymmetricKey symmetricKey = new SymmetricKey(sessionKey);
SqlClientEncryptionAlgorithm sqlClientEncryptionAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create(
symmetricKey,
SqlClientEncryptionType.Randomized,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
index b0109902c6..c134899ab9 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
@@ -1,7 +1,8 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
using System.Text;
namespace Microsoft.Data.SqlClient
@@ -13,7 +14,7 @@ namespace Microsoft.Data.SqlClient
/// 3) mac_key - A derived key that is used to compute HMAC of the cipher text
/// 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
///
- internal class SqlAeadAes256CbcHmac256EncryptionKey : SqlClientSymmetricKey
+ internal class SqlAeadAes256CbcHmac256EncryptionKey : SymmetricKey
{
///
/// Key size in bits
@@ -38,17 +39,17 @@ internal class SqlAeadAes256CbcHmac256EncryptionKey : SqlClientSymmetricKey
///
/// Encryption Key
///
- private readonly SqlClientSymmetricKey _encryptionKey;
+ private readonly SymmetricKey _encryptionKey;
///
/// MAC key
///
- private readonly SqlClientSymmetricKey _macKey;
+ private readonly SymmetricKey _macKey;
///
/// IV Key
///
- private readonly SqlClientSymmetricKey _ivKey;
+ private readonly SymmetricKey _ivKey;
///
/// The name of the algorithm this key will be used with.
@@ -82,19 +83,19 @@ internal SqlAeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmNa
KeySize);
byte[] buff1 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
- _encryptionKey = new SqlClientSymmetricKey(buff1);
+ _encryptionKey = new SymmetricKey(buff1);
// Derive mac key
string macKeySalt = string.Format(_macKeySaltFormat, _algorithmName, KeySize);
byte[] buff2 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2);
- _macKey = new SqlClientSymmetricKey(buff2);
+ _macKey = new SymmetricKey(buff2);
// Derive iv key
string ivKeySalt = string.Format(_ivKeySaltFormat, _algorithmName, KeySize);
byte[] buff3 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3);
- _ivKey = new SqlClientSymmetricKey(buff3);
+ _ivKey = new SymmetricKey(buff3);
}
///
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
index a53d8983da..1b5c8b46b3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
@@ -198,7 +198,7 @@ internal static void DecryptSymmetricKey(SqlCipherMetadata md, SqlConnection con
{
Debug.Assert(md is not null, "md should not be null in DecryptSymmetricKey.");
- SqlClientSymmetricKey symKey = null;
+ SymmetricKey symKey = null;
SqlEncryptionKeyInfo encryptionkeyInfoChosen = null;
DecryptSymmetricKey(md.EncryptionInfo, out symKey, out encryptionkeyInfoChosen, connection, command);
@@ -217,7 +217,7 @@ internal static void DecryptSymmetricKey(SqlCipherMetadata md, SqlConnection con
///
/// Decrypts the symmetric key and saves it in metadata.
///
- internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoEntry, out SqlClientSymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo encryptionkeyInfoChosen, SqlConnection connection, SqlCommand command)
+ internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoEntry, out SymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo encryptionkeyInfoChosen, SqlConnection connection, SqlCommand command)
{
Debug.Assert(connection is not null, "Connection should not be null.");
Debug.Assert(sqlTceCipherInfoEntry is not null, "sqlTceCipherInfoEntry should not be null in DecryptSymmetricKey.");
@@ -254,7 +254,7 @@ internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoE
Debug.Assert(encryptionkeyInfoChosen is not null, "encryptionkeyInfoChosen must have a value.");
}
- private static SqlClientSymmetricKey GetKeyFromLocalProviders(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command)
+ private static SymmetricKey GetKeyFromLocalProviders(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command)
{
string serverName = connection.DataSource;
Debug.Assert(serverName is not null, @"serverName should not be null.");
@@ -283,7 +283,7 @@ private static SqlClientSymmetricKey GetKeyFromLocalProviders(SqlEncryptionKeyIn
throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e);
}
- return new SqlClientSymmetricKey(plaintextKey);
+ return new SymmetricKey(plaintextKey);
}
///
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
index caa98cc686..e3fe79ec2d 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
@@ -17,8 +17,8 @@ public class ExceptionsAlgorithmErrors : IClassFixture
Date: Tue, 28 Apr 2026 05:49:22 +0100
Subject: [PATCH 09/27] Enable nullability annotations.
---
.../SqlClient/AlwaysEncrypted/SymmetricKey.cs | 21 ++++++-------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
index ebbc033e81..e26b722aa8 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#nullable enable
+
namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
@@ -10,36 +12,25 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
///
internal class SymmetricKey
{
- ///
- /// The underlying key material
- ///
- protected readonly byte[] _rootKey;
-
///
/// Constructor that initializes the root key.
///
/// root key
- internal SymmetricKey(byte[] rootKey)
+ internal SymmetricKey(byte[]? rootKey)
{
// Key validation
- if (rootKey == null || rootKey.Length == 0)
+ if (rootKey is null || rootKey.Length == 0)
{
throw SQL.NullColumnEncryptionKeySysErr();
}
- _rootKey = rootKey;
+ RootKey = rootKey;
}
///
/// Returns a copy of the plain text key
/// This is needed for actual encryption/decryption.
///
- internal virtual byte[] RootKey
- {
- get
- {
- return _rootKey;
- }
- }
+ internal byte[] RootKey { get; }
}
}
From 70d5e1917cfc4b54bf93b19fc1344fb128750c1c Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 05:50:54 +0100
Subject: [PATCH 10/27] Member visibility cleanup.
---
.../Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs | 4 ++--
.../AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs | 2 +-
.../tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
index e26b722aa8..311a94e25c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
@@ -16,7 +16,7 @@ internal class SymmetricKey
/// Constructor that initializes the root key.
///
/// root key
- internal SymmetricKey(byte[]? rootKey)
+ public SymmetricKey(byte[]? rootKey)
{
// Key validation
if (rootKey is null || rootKey.Length == 0)
@@ -31,6 +31,6 @@ internal SymmetricKey(byte[]? rootKey)
/// Returns a copy of the plain text key
/// This is needed for actual encryption/decryption.
///
- internal byte[] RootKey { get; }
+ public byte[] RootKey { get; }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
index e3fe79ec2d..d86d8b3676 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
@@ -18,7 +18,7 @@ public class ExceptionsAlgorithmErrors : IClassFixture
Date: Tue, 28 Apr 2026 05:52:54 +0100
Subject: [PATCH 11/27] Rename and move SqlAeadAes256CbcHmac256EncryptionKey
---
.../AeadAes256CbcHmac256EncryptionKey.cs} | 7 +++----
.../AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs | 4 ++--
.../Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs | 7 ++++---
3 files changed, 9 insertions(+), 9 deletions(-)
rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlAeadAes256CbcHmac256EncryptionKey.cs => AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs} (94%)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
similarity index 94%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index c134899ab9..a9b4d3efcf 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.Data.SqlClient.AlwaysEncrypted;
using System.Text;
-namespace Microsoft.Data.SqlClient
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
/// Encryption key class containing 4 keys. This class is used by SqlAeadAes256CbcHmac256Algorithm
@@ -14,7 +13,7 @@ namespace Microsoft.Data.SqlClient
/// 3) mac_key - A derived key that is used to compute HMAC of the cipher text
/// 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
///
- internal class SqlAeadAes256CbcHmac256EncryptionKey : SymmetricKey
+ internal class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
{
///
/// Key size in bits
@@ -61,7 +60,7 @@ internal class SqlAeadAes256CbcHmac256EncryptionKey : SymmetricKey
///
/// Root key used to derive all the required derived keys
///
- internal SqlAeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName) : base(rootKey)
+ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName) : base(rootKey)
{
_algorithmName = algorithmName;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
index ab6e7259f1..513655fce6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
@@ -22,7 +22,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
internal sealed class AeadAes256CbcHmac256Factory : EncryptionAlgorithmFactory
{
///
- /// Factory classes cache the objects to avoid recomputation of the derived keys.
+ /// Factory classes cache the objects to avoid recomputation of the derived keys.
///
private readonly ConcurrentDictionary _encryptionAlgorithms =
new(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary */, capacity: 2);
@@ -70,7 +70,7 @@ internal override SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey
if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out SqlAeadAes256CbcHmac256Algorithm? aesAlgorithm))
{
- SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ AeadAes256CbcHmac256EncryptionKey encryptedKey = new(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
aesAlgorithm = new SqlAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, SqlAeadAes256CbcHmac256Algorithm.CurrentVersion);
// In case multiple threads reach here at the same time, the first one adds the value
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
index 307330af0a..8d4871552b 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -33,7 +34,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
///
/// Key size in bytes
///
- private const int _KeySizeInBytes = SqlAeadAes256CbcHmac256EncryptionKey.KeySize / 8;
+ private const int _KeySizeInBytes = AeadAes256CbcHmac256EncryptionKey.KeySize / 8;
///
/// Block size in bytes. AES uses 16 byte blocks.
@@ -75,7 +76,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
///
/// Column Encryption Key. This has a root key and three derived keys.
///
- private readonly SqlAeadAes256CbcHmac256EncryptionKey _columnEncryptionKey;
+ private readonly AeadAes256CbcHmac256EncryptionKey _columnEncryptionKey;
///
/// The pool of crypto providers to use for encrypt/decrypt operations.
@@ -105,7 +106,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
///
/// Algorithm version
///
- internal SqlAeadAes256CbcHmac256Algorithm(SqlAeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion)
+ internal SqlAeadAes256CbcHmac256Algorithm(AeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion)
{
_columnEncryptionKey = encryptionKey;
_algorithmVersion = algorithmVersion;
From 98fd8002ceb486b86906e7451e787ead3b4fd3ac Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 05:58:57 +0100
Subject: [PATCH 12/27] Seal AeadAes256CbcHmac256EncryptionKey
---
.../AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index a9b4d3efcf..674b53fdd2 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -13,7 +13,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
/// 3) mac_key - A derived key that is used to compute HMAC of the cipher text
/// 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
///
- internal class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
+ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
{
///
/// Key size in bits
From 9672162b43c9cf85e25c2cfed0c2b754f2336be4 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:01:26 +0100
Subject: [PATCH 13/27] Remove redundant ctor parameter
The encryption key and the algorithm are tightly coupled; we don't need to specify this explicitly.
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 18 +++++-------------
.../AeadAes256CbcHmac256Factory.cs | 2 +-
2 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 674b53fdd2..b976a18dd8 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -50,26 +50,18 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
///
private readonly SymmetricKey _ivKey;
- ///
- /// The name of the algorithm this key will be used with.
- ///
- private readonly string _algorithmName;
-
///
/// Derives all the required keys from the given root key
///
/// Root key used to derive all the required derived keys
- ///
- internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName) : base(rootKey)
+ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
{
- _algorithmName = algorithmName;
-
int keySizeInBytes = KeySize / 8;
// Key validation
if (rootKey.Length != keySizeInBytes)
{
- throw SQL.InvalidKeySize(_algorithmName,
+ throw SQL.InvalidKeySize(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
rootKey.Length,
keySizeInBytes);
}
@@ -78,20 +70,20 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName)
//
// Derive encryption key
string encryptionKeySalt = string.Format(_encryptionKeySaltFormat,
- _algorithmName,
+ SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
KeySize);
byte[] buff1 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
_encryptionKey = new SymmetricKey(buff1);
// Derive mac key
- string macKeySalt = string.Format(_macKeySaltFormat, _algorithmName, KeySize);
+ string macKeySalt = string.Format(_macKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
byte[] buff2 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2);
_macKey = new SymmetricKey(buff2);
// Derive iv key
- string ivKeySalt = string.Format(_ivKeySaltFormat, _algorithmName, KeySize);
+ string ivKeySalt = string.Format(_ivKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
byte[] buff3 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3);
_ivKey = new SymmetricKey(buff3);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
index 513655fce6..4fda0f27b3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
@@ -70,7 +70,7 @@ internal override SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey
if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out SqlAeadAes256CbcHmac256Algorithm? aesAlgorithm))
{
- AeadAes256CbcHmac256EncryptionKey encryptedKey = new(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ AeadAes256CbcHmac256EncryptionKey encryptedKey = new(encryptionKey.RootKey);
aesAlgorithm = new SqlAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, SqlAeadAes256CbcHmac256Algorithm.CurrentVersion);
// In case multiple threads reach here at the same time, the first one adds the value
From cbf1f40306b5050968c93a78f50fbbf7481d13fc Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:04:41 +0100
Subject: [PATCH 14/27] Replace redundant allocation of SymmetricKeys with
autoprops
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 36 ++++---------------
1 file changed, 6 insertions(+), 30 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index b976a18dd8..772e14f4d6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -35,21 +35,6 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
///
private const string _ivKeySaltFormat = @"Microsoft SQL Server cell IV key with encryption algorithm:{0} and key length:{1}";
- ///
- /// Encryption Key
- ///
- private readonly SymmetricKey _encryptionKey;
-
- ///
- /// MAC key
- ///
- private readonly SymmetricKey _macKey;
-
- ///
- /// IV Key
- ///
- private readonly SymmetricKey _ivKey;
-
///
/// Derives all the required keys from the given root key
///
@@ -74,43 +59,34 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
KeySize);
byte[] buff1 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
- _encryptionKey = new SymmetricKey(buff1);
+ EncryptionKey = buff1;
// Derive mac key
string macKeySalt = string.Format(_macKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
byte[] buff2 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2);
- _macKey = new SymmetricKey(buff2);
+ MACKey = buff2;
// Derive iv key
string ivKeySalt = string.Format(_ivKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
byte[] buff3 = new byte[keySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3);
- _ivKey = new SymmetricKey(buff3);
+ IVKey = buff3;
}
///
/// Encryption key should be used for encryption and decryption
///
- internal byte[] EncryptionKey
- {
- get { return _encryptionKey.RootKey; }
- }
+ public byte[] EncryptionKey { get; }
///
/// MAC key should be used to compute and validate HMAC
///
- internal byte[] MACKey
- {
- get { return _macKey.RootKey; }
- }
+ public byte[] MACKey { get; }
///
/// IV key should be used to compute synthetic IV from a given plain text
///
- internal byte[] IVKey
- {
- get { return _ivKey.RootKey; }
- }
+ public byte[] IVKey { get; }
}
}
From 91096d029b268aa984f46723938e2db852b2abde Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:06:43 +0100
Subject: [PATCH 15/27] Constant cleanup
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 27 ++++++++++---------
.../SqlAeadAes256CbcHmac256Algorithm.cs | 2 +-
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 772e14f4d6..8b54efa77b 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -16,9 +16,14 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
{
///
- /// Key size in bits
+ /// Key size in bits.
///
- internal const int KeySize = 256;
+ public const int KeySizeInBits = 256;
+
+ ///
+ /// Key size in bytes.
+ ///
+ public const int KeySizeInBytes = KeySizeInBits / 8;
///
/// Encryption Key Salt format. This is used to derive the encryption key from the root key.
@@ -41,14 +46,12 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
/// Root key used to derive all the required derived keys
internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
{
- int keySizeInBytes = KeySize / 8;
-
// Key validation
- if (rootKey.Length != keySizeInBytes)
+ if (rootKey.Length != KeySizeInBytes)
{
throw SQL.InvalidKeySize(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
rootKey.Length,
- keySizeInBytes);
+ KeySizeInBytes);
}
// Derive keys from the root key
@@ -56,20 +59,20 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
// Derive encryption key
string encryptionKeySalt = string.Format(_encryptionKeySaltFormat,
SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
- KeySize);
- byte[] buff1 = new byte[keySizeInBytes];
+ KeySizeInBits);
+ byte[] buff1 = new byte[KeySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
EncryptionKey = buff1;
// Derive mac key
- string macKeySalt = string.Format(_macKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
- byte[] buff2 = new byte[keySizeInBytes];
+ string macKeySalt = string.Format(_macKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySizeInBits);
+ byte[] buff2 = new byte[KeySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2);
MACKey = buff2;
// Derive iv key
- string ivKeySalt = string.Format(_ivKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySize);
- byte[] buff3 = new byte[keySizeInBytes];
+ string ivKeySalt = string.Format(_ivKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySizeInBits);
+ byte[] buff3 = new byte[KeySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3);
IVKey = buff3;
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
index 8d4871552b..a9ccdfafe3 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
@@ -34,7 +34,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
///
/// Key size in bytes
///
- private const int _KeySizeInBytes = AeadAes256CbcHmac256EncryptionKey.KeySize / 8;
+ private const int _KeySizeInBytes = AeadAes256CbcHmac256EncryptionKey.KeySizeInBits / 8;
///
/// Block size in bytes. AES uses 16 byte blocks.
From 7763ddd220fd9096e8f2354c3180d20a0d28984f Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:10:58 +0100
Subject: [PATCH 16/27] Remove redundant string formatting
Salt strings are always constant.
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 8b54efa77b..12ec8c5614 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -26,19 +26,24 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
public const int KeySizeInBytes = KeySizeInBits / 8;
///
- /// Encryption Key Salt format. This is used to derive the encryption key from the root key.
+ /// as a string, for use in the salt formats below.
///
- private const string _encryptionKeySaltFormat = @"Microsoft SQL Server cell encryption key with encryption algorithm:{0} and key length:{1}";
+ private const string KeySizeInBitsString = "256";
///
- /// MAC Key Salt format. This is used to derive the MAC key from the root key.
+ /// Encryption Key Salt. This is used to derive the encryption key from the root key.
///
- private const string _macKeySaltFormat = @"Microsoft SQL Server cell MAC key with encryption algorithm:{0} and key length:{1}";
+ private const string EncryptionKeySaltString = $"Microsoft SQL Server cell encryption key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
///
- /// IV Key Salt format. This is used to derive the IV key from the root key. This is only used for Deterministic encryption.
+ /// MAC Key Salt. This is used to derive the MAC key from the root key.
///
- private const string _ivKeySaltFormat = @"Microsoft SQL Server cell IV key with encryption algorithm:{0} and key length:{1}";
+ private const string MacKeySaltString = $"Microsoft SQL Server cell MAC key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
+
+ ///
+ /// IV Key Salt. This is used to derive the IV key from the root key. This is only used for Deterministic encryption.
+ ///
+ private const string IvKeySaltString = $"Microsoft SQL Server cell IV key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
///
/// Derives all the required keys from the given root key
@@ -57,23 +62,18 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
// Derive keys from the root key
//
// Derive encryption key
- string encryptionKeySalt = string.Format(_encryptionKeySaltFormat,
- SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
- KeySizeInBits);
byte[] buff1 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
+ SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(EncryptionKeySaltString), RootKey, buff1);
EncryptionKey = buff1;
// Derive mac key
- string macKeySalt = string.Format(_macKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySizeInBits);
byte[] buff2 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2);
+ SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(MacKeySaltString), RootKey, buff2);
MACKey = buff2;
// Derive iv key
- string ivKeySalt = string.Format(_ivKeySaltFormat, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, KeySizeInBits);
byte[] buff3 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3);
+ SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(IvKeySaltString), RootKey, buff3);
IVKey = buff3;
}
From 0f3380f49dc2378a3f3e5258eab52e5e2cd41bc1 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:13:17 +0100
Subject: [PATCH 17/27] Remove repeated string decoding of constants
Lazy-load these - these values will not change.
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 12ec8c5614..9d996ea29f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -45,6 +45,13 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
///
private const string IvKeySaltString = $"Microsoft SQL Server cell IV key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
+ private static byte[] EncryptionKeySalt =>
+ field ??= Encoding.Unicode.GetBytes(EncryptionKeySaltString);
+ private static byte[] MacKeySalt =>
+ field ??= Encoding.Unicode.GetBytes(MacKeySaltString);
+ private static byte[] IvKeySalt =>
+ field ??= Encoding.Unicode.GetBytes(IvKeySaltString);
+
///
/// Derives all the required keys from the given root key
///
@@ -63,17 +70,17 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
//
// Derive encryption key
byte[] buff1 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(EncryptionKeySaltString), RootKey, buff1);
+ SqlSecurityUtility.GetHMACWithSHA256(EncryptionKeySalt, RootKey, buff1);
EncryptionKey = buff1;
// Derive mac key
byte[] buff2 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(MacKeySaltString), RootKey, buff2);
+ SqlSecurityUtility.GetHMACWithSHA256(MacKeySalt, RootKey, buff2);
MACKey = buff2;
// Derive iv key
byte[] buff3 = new byte[KeySizeInBytes];
- SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(IvKeySaltString), RootKey, buff3);
+ SqlSecurityUtility.GetHMACWithSHA256(IvKeySalt, RootKey, buff3);
IVKey = buff3;
}
From b83c751d1804dfcb57ad036fe24a23eb6b94019f Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:14:32 +0100
Subject: [PATCH 18/27] Make capitalisation of IV and MAC consistent
---
.../AeadAes256CbcHmac256EncryptionKey.cs | 12 ++++++------
.../SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs | 6 +++---
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 9d996ea29f..7045db3397 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -73,15 +73,15 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
SqlSecurityUtility.GetHMACWithSHA256(EncryptionKeySalt, RootKey, buff1);
EncryptionKey = buff1;
- // Derive mac key
+ // Derive MAC key
byte[] buff2 = new byte[KeySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(MacKeySalt, RootKey, buff2);
- MACKey = buff2;
+ MacKey = buff2;
- // Derive iv key
+ // Derive IV key
byte[] buff3 = new byte[KeySizeInBytes];
SqlSecurityUtility.GetHMACWithSHA256(IvKeySalt, RootKey, buff3);
- IVKey = buff3;
+ IvKey = buff3;
}
///
@@ -92,11 +92,11 @@ internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
///
/// MAC key should be used to compute and validate HMAC
///
- public byte[] MACKey { get; }
+ public byte[] MacKey { get; }
///
/// IV key should be used to compute synthetic IV from a given plain text
///
- public byte[] IVKey { get; }
+ public byte[] IvKey { get; }
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
index a9ccdfafe3..fea266a123 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
@@ -164,7 +164,7 @@ protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag)
// Should be 1 single block (16 bytes)
if (_isDeterministic)
{
- SqlSecurityUtility.GetHMACWithSHA256(plainText, _columnEncryptionKey.IVKey, iv);
+ SqlSecurityUtility.GetHMACWithSHA256(plainText, _columnEncryptionKey.IvKey, iv);
}
else
{
@@ -236,7 +236,7 @@ protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag)
if (hasAuthenticationTag)
{
- using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey))
+ using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MacKey))
{
Debug.Assert(hmac.CanTransformMultipleBlocks, "HMAC can't transform multiple blocks");
hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
@@ -427,7 +427,7 @@ private byte[] PrepareAuthenticationTag(byte[] iv, byte[] cipherText, int offset
// cipherText.Length
// 1 byte for version byte length
- using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey))
+ using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MacKey))
{
int retVal = 0;
retVal = hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
From aae7806eb17cb2c3758b06d719bb0ca370dfb73c Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:18:01 +0100
Subject: [PATCH 19/27] Add links to documentation of constants
---
.../AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 7045db3397..5a7ccec9cd 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -33,16 +33,19 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
///
/// Encryption Key Salt. This is used to derive the encryption key from the root key.
///
+ ///
private const string EncryptionKeySaltString = $"Microsoft SQL Server cell encryption key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
///
/// MAC Key Salt. This is used to derive the MAC key from the root key.
///
+ ///
private const string MacKeySaltString = $"Microsoft SQL Server cell MAC key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
///
/// IV Key Salt. This is used to derive the IV key from the root key. This is only used for Deterministic encryption.
///
+ ///
private const string IvKeySaltString = $"Microsoft SQL Server cell IV key with encryption algorithm:{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName} and key length:{KeySizeInBitsString}";
private static byte[] EncryptionKeySalt =>
From cc72373e6ef1dd627f471a3d72f84b08d207f720 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:18:38 +0100
Subject: [PATCH 20/27] Enable nullability annotations
---
.../AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 5a7ccec9cd..009dd5f7c6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -4,6 +4,8 @@
using System.Text;
+#nullable enable
+
namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
From ca4e596f3e980d1079199eb71b6b340d8587363f Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:19:22 +0100
Subject: [PATCH 21/27] Member visibility adjustment
---
.../AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
index 009dd5f7c6..eeb5db53de 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKey.cs
@@ -61,7 +61,7 @@ internal sealed class AeadAes256CbcHmac256EncryptionKey : SymmetricKey
/// Derives all the required keys from the given root key
///
/// Root key used to derive all the required derived keys
- internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
+ public AeadAes256CbcHmac256EncryptionKey(byte[] rootKey) : base(rootKey)
{
// Key validation
if (rootKey.Length != KeySizeInBytes)
From 37c879da02f3ca5dd0a3efc20272265042787aa3 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:55:15 +0100
Subject: [PATCH 22/27] Remove unnecessary allocations from GetHMACWithSHA256
---
.../Data/SqlClient/SqlSecurityUtility.cs | 40 +++++++++++++++----
1 file changed, 33 insertions(+), 7 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
index 1b5c8b46b3..6a258e6d7d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs
@@ -19,6 +19,8 @@ internal static class SqlSecurityUtility
static readonly ColumnMasterKeyMetadataSignatureVerificationCache ColumnMasterKeyMetadataSignatureVerificationCache = ColumnMasterKeyMetadataSignatureVerificationCache.Instance;
+ #nullable enable
+
///
/// Computes a keyed hash of a given text and returns. It fills the buffer "hash" with computed hash value.
///
@@ -26,21 +28,45 @@ internal static class SqlSecurityUtility
/// key used for the HMAC
/// Output buffer where the computed hash value is stored. If its less that 64 bytes, the hash is truncated
/// HMAC value
- internal static void GetHMACWithSHA256(byte[] plainText, byte[] key, byte[] hash)
+ #if NET
+ public static void GetHMACWithSHA256(ReadOnlySpan plainText, ReadOnlySpan key, Span hash)
+ {
+ Debug.Assert(hash.Length != 0 && hash.Length <= HMACSHA256.HashSizeInBytes);
+
+ // We can't guarantee that the destination buffer will be large enough to hold the entire hash.
+ // If it is large enough though, we can write directly into it to avoid an extra copy.
+ if (hash.Length == HMACSHA256.HashSizeInBytes)
+ {
+ bool writtenHash = HMACSHA256.TryHashData(key, plainText, hash, out _);
+
+ Debug.Assert(writtenHash);
+ }
+ else
+ {
+ Span hashBuffer = stackalloc byte[HMACSHA256.HashSizeInBytes];
+ bool writtenHash = HMACSHA256.TryHashData(key, plainText, hashBuffer, out _);
+
+ Debug.Assert(writtenHash);
+ hashBuffer.Slice(0, hash.Length).CopyTo(hash);
+ }
+ }
+ #else
+ public static void GetHMACWithSHA256(byte[] plainText, byte[] key, byte[] hash)
{
const int MaxSHA256HashBytes = 32;
Debug.Assert(key != null && plainText != null);
Debug.Assert(hash.Length != 0 && hash.Length <= MaxSHA256HashBytes);
- using (HMACSHA256 hmac = new HMACSHA256(key))
- {
- byte[] computedHash = hmac.ComputeHash(plainText);
+ using HMACSHA256 hmac = new(key);
+ byte[] computedHash = hmac.ComputeHash(plainText);
- // Truncate the hash if needed
- Buffer.BlockCopy(computedHash, 0, hash, 0, hash.Length);
- }
+ // Truncate the hash if needed
+ Buffer.BlockCopy(computedHash, 0, hash, 0, hash.Length);
}
+ #endif
+
+ #nullable restore
///
/// Generates cryptographically random bytes
From c7620353ef22ca9cc92f2a5ac9f040fa078fa5c8 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 28 Apr 2026 06:58:47 +0100
Subject: [PATCH 23/27] Document SqlClientEncryptionType
---
.../Data/SqlClient/SqlClientEncryptionType.cs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs
index 50dc263bb3..4a418f43ce 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs
@@ -1,16 +1,17 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Data.SqlClient
{
///
- /// Encryption types supported in TCE
+ /// Encryption types supported in TCE. Corresponds to EncryptionAlgoType in MS-TDS.
///
+ ///
internal enum SqlClientEncryptionType
{
- PlainText = 0,
- Deterministic,
- Randomized
+ PlainText = 0x00,
+ Deterministic = 0x01,
+ Randomized = 0x02
}
}
From c1e0699550b7d4a91b976f6e575376d793219b44 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 29 Apr 2026 05:18:42 +0100
Subject: [PATCH 24/27] Rename and move SqlClientEncryptionType
---
.../AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs | 8 ++++----
.../AlwaysEncrypted/EncryptionAlgorithmFactory.cs | 2 +-
.../AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs | 2 +-
.../EncryptionType.cs} | 4 ++--
.../src/Microsoft/Data/SqlClient/EnclaveDelegate.cs | 2 +-
.../Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs | 6 +++---
.../src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs | 3 ++-
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 3 ++-
.../Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs | 4 ++--
9 files changed, 18 insertions(+), 16 deletions(-)
rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlClientEncryptionType.cs => AlwaysEncrypted/EncryptionType.cs} (86%)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
index 4fda0f27b3..715aabfe33 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
@@ -41,18 +41,18 @@ private AeadAes256CbcHmac256Factory() { }
/// Encryption type. Expected values are either Deterministic or Randomized.
/// Cryptographic algorithm.
/// An implementation of the AEAD_AES_256_CBC_HMAC_SHA256 cryptographic algorithm.
- internal override SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm)
+ internal override SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, EncryptionType encryptionType, string encryptionAlgorithm)
{
// Callers should have validated the encryption algorithm and the encryption key
Debug.Assert(string.Equals(encryptionAlgorithm, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase));
// Validate encryption type
- if (encryptionType is not SqlClientEncryptionType.Deterministic and not SqlClientEncryptionType.Randomized)
+ if (encryptionType is not EncryptionType.Deterministic and not EncryptionType.Randomized)
{
throw SQL.InvalidEncryptionType(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
encryptionType,
- SqlClientEncryptionType.Deterministic,
- SqlClientEncryptionType.Randomized);
+ EncryptionType.Deterministic,
+ EncryptionType.Randomized);
}
// Get the cached cryptographic algorithm if one exists or create a new one, add it to cache and use it
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
index a56db20dce..4acc55f161 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs
@@ -20,5 +20,5 @@ internal abstract class EncryptionAlgorithmFactory
/// Encryption Type, some algorithms will need this
/// Cryptographic algorithm name. Needed for extracting version bits
/// Return a newly created SqlClientEncryptionAlgorithm instance
- internal abstract SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm);
+ internal abstract SqlClientEncryptionAlgorithm Create(SymmetricKey encryptionKey, EncryptionType encryptionType, string encryptionAlgorithm);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
index 71a46523c9..6aeb65b246 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs
@@ -32,6 +32,6 @@ public static void GetAlgorithm(SymmetricKey key, byte type, string algorithmNam
_ => throw SQL.UnknownColumnEncryptionAlgorithm(algorithmName, RegisteredCipherAlgorithmNames)
};
- encryptionAlgorithm = factory.Create(key, (SqlClientEncryptionType)type, algorithmName);
+ encryptionAlgorithm = factory.Create(key, (EncryptionType)type, algorithmName);
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionType.cs
similarity index 86%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionType.cs
index 4a418f43ce..ec14bcf4c6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionType.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionType.cs
@@ -2,13 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-namespace Microsoft.Data.SqlClient
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted
{
///
/// Encryption types supported in TCE. Corresponds to EncryptionAlgoType in MS-TDS.
///
///
- internal enum SqlClientEncryptionType
+ internal enum EncryptionType
{
PlainText = 0x00,
Deterministic = 0x01,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
index 4b3ace729f..32bccd0873 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs
@@ -154,7 +154,7 @@ private byte[] EncryptBytePackage(byte[] bytePackage, byte[] sessionKey, string
SymmetricKey symmetricKey = new SymmetricKey(sessionKey);
SqlClientEncryptionAlgorithm sqlClientEncryptionAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create(
symmetricKey,
- SqlClientEncryptionType.Randomized,
+ EncryptionType.Randomized,
SqlAeadAes256CbcHmac256Algorithm.AlgorithmName
);
return sqlClientEncryptionAlgorithm.EncryptData(bytePackage);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
index fea266a123..d6386b44ed 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
@@ -106,7 +106,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
///
/// Algorithm version
///
- internal SqlAeadAes256CbcHmac256Algorithm(AeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion)
+ internal SqlAeadAes256CbcHmac256Algorithm(AeadAes256CbcHmac256EncryptionKey encryptionKey, EncryptionType encryptionType, byte algorithmVersion)
{
_columnEncryptionKey = encryptionKey;
_algorithmVersion = algorithmVersion;
@@ -117,13 +117,13 @@ internal SqlAeadAes256CbcHmac256Algorithm(AeadAes256CbcHmac256EncryptionKey encr
// Validate encryption type for this algorithm
// This algorithm can only provide randomized or deterministic encryption types.
- if (encryptionType == SqlClientEncryptionType.Deterministic)
+ if (encryptionType == EncryptionType.Deterministic)
{
_isDeterministic = true;
}
else
{
- Debug.Assert(SqlClientEncryptionType.Randomized == encryptionType, "Invalid Encryption Type detected in SqlAeadAes256CbcHmac256Algorithm, this should've been caught in factory class");
+ Debug.Assert(EncryptionType.Randomized == encryptionType, "Invalid Encryption Type detected in SqlAeadAes256CbcHmac256Algorithm, this should've been caught in factory class");
}
_cryptoProviderPool = new ConcurrentQueue();
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
index 59a2fddadc..f45a19cf7b 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs
@@ -12,6 +12,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
using Microsoft.Data.SqlClient.Connection;
namespace Microsoft.Data.SqlClient
@@ -1132,7 +1133,7 @@ private int ReadDescribeEncryptionParameterResultsMetadata(
// Found the param, set up the encryption info.
byte columnEncryptionType = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncryptionType);
- if (columnEncryptionType != (byte)SqlClientEncryptionType.PlainText)
+ if (columnEncryptionType != (byte)EncryptionType.PlainText)
{
byte cipherAlgorithmId = ds.GetByte(
(int)DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 96312a491d..2a7c756a37 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -16,6 +16,7 @@
using System.Transactions;
using Interop.Common.Sni;
using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
using Microsoft.Data.SqlClient.Connection;
#if NET
@@ -1689,7 +1690,7 @@ internal static Exception InvalidKeySize(string algorithmName, int actualKeyleng
expectedLength), TdsEnums.TCE_PARAM_ENCRYPTIONKEY);
}
- internal static Exception InvalidEncryptionType(string algorithmName, SqlClientEncryptionType encryptionType, params SqlClientEncryptionType[] validEncryptionTypes)
+ internal static Exception InvalidEncryptionType(string algorithmName, EncryptionType encryptionType, params EncryptionType[] validEncryptionTypes)
{
const string valueSeparator = @", ";
return ADP.Argument(
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs
index 331e75c460..d2ac56f582 100644
--- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs
@@ -113,7 +113,7 @@ public void Known_Plaintext_Encrypts_To_Known_FinalCell(byte[] plainText, byte[]
{
SymmetricKey cek = new(rootKey);
AeadAes256CbcHmac256Factory aeadFactory = AeadAes256CbcHmac256Factory.Instance;
- SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, SqlClientEncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, EncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
byte[] encryptedData = aeadAlgorithm.EncryptData(plainText);
Assert.Equal(expectedFinalCell, encryptedData);
@@ -132,7 +132,7 @@ public void Known_FinalCell_Decrypts_To_Known_Plaintext(byte[] expectedPlaintext
{
SymmetricKey cek = new(rootKey);
AeadAes256CbcHmac256Factory aeadFactory = AeadAes256CbcHmac256Factory.Instance;
- SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, SqlClientEncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, EncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
byte[] decryptedData = aeadAlgorithm.DecryptData(finalCell);
Assert.Equal(expectedPlaintext, decryptedData);
From 45c5cd72af61d7a408b4c7c5e46e97b6366e28f8 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 13 May 2026 00:11:19 +0100
Subject: [PATCH 25/27] Preinitialize singleton instances
---
.../AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs | 4 +++-
.../Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs | 7 ++++---
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
index 715aabfe33..0648e94632 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs
@@ -21,6 +21,8 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
///
internal sealed class AeadAes256CbcHmac256Factory : EncryptionAlgorithmFactory
{
+ private static readonly AeadAes256CbcHmac256Factory s_singletonInstance = new();
+
///
/// Factory classes cache the objects to avoid recomputation of the derived keys.
///
@@ -32,7 +34,7 @@ private AeadAes256CbcHmac256Factory() { }
///
/// Access the instance of the factory class for the AEAD_AES_256_CBC_HMAC_SHA256 encryption algorithm.
///
- public static AeadAes256CbcHmac256Factory Instance => field ??= new();
+ public static AeadAes256CbcHmac256Factory Instance => s_singletonInstance;
///
/// Creates an instance of the class with a given root key.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
index c21dc0863c..6eae7b158e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyCache.cs
@@ -17,16 +17,17 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted
///
internal sealed class SymmetricKeyCache
{
- private readonly MemoryCache _cache;
+ private static readonly SymmetricKeyCache s_singletonInstance = new();
private static readonly SemaphoreSlim s_cacheLock = new(1, 1);
+ private readonly MemoryCache _cache;
+
private SymmetricKeyCache()
{
_cache = new MemoryCache(new MemoryCacheOptions());
}
- public static SymmetricKeyCache Instance =>
- field ??= new();
+ public static SymmetricKeyCache Instance => s_singletonInstance;
///
/// Retrieves Symmetric Key (in plaintext) given the encryption material.
From 183ff750b51d8b2b39ab4d6eb1dc293298c2ea59 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 13 May 2026 00:30:21 +0100
Subject: [PATCH 26/27] Comment fixup
---
.../Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
index 311a94e25c..4273525a3c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKey.cs
@@ -15,7 +15,7 @@ internal class SymmetricKey
///
/// Constructor that initializes the root key.
///
- /// root key
+ /// Root key
public SymmetricKey(byte[]? rootKey)
{
// Key validation
@@ -28,7 +28,7 @@ public SymmetricKey(byte[]? rootKey)
}
///
- /// Returns a copy of the plain text key
+ /// Returns the plain text key.
/// This is needed for actual encryption/decryption.
///
public byte[] RootKey { get; }
From d3bef8b8a471d2915d4a258262989887ba1f7d75 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 13 May 2026 01:17:29 +0100
Subject: [PATCH 27/27] Add unit tests
These should address the Codecov reports
---
.../AeadAes256CbcHmac256EncryptionKeyTest.cs | 52 +++++++++++++++
.../AeadAes256CbcHmac256FactoryTest.cs | 64 +++++++++++++++++++
.../EncryptionAlgorithmFactoryListTest.cs | 34 ++++++++++
.../AlwaysEncrypted/SymmetricKeyTest.cs | 43 +++++++++++++
4 files changed, 193 insertions(+)
create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKeyTest.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256FactoryTest.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryListTest.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyTest.cs
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKeyTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKeyTest.cs
new file mode 100644
index 0000000000..9c068a7def
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256EncryptionKeyTest.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
+using System;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.UnitTests.AlwaysEncrypted;
+
+///
+/// Unit tests to verify that AeadAes256CbcHmac256EncryptionKey's
+/// cryptographic and validation logic behave correctly.
+///
+public class AeadAes256CbcHmac256EncryptionKeyTest
+{
+ ///
+ /// Verifies that if the AeadAes256CbcHmac256EncryptionKey is
+ /// constructed with a root key of incorrect length, it throws
+ /// an exception.
+ ///
+ [Fact]
+ public void Constructor_ThrowsOnInvalidSize()
+ {
+ byte[] invalidSizeRootKey = [0x01, 0x02, 0x03, 0x04];
+ Action createEncryptionKey = () => new AeadAes256CbcHmac256EncryptionKey(invalidSizeRootKey);
+
+ Assert.NotEqual(AeadAes256CbcHmac256EncryptionKey.KeySizeInBytes, invalidSizeRootKey.Length);
+ Assert.Throws(createEncryptionKey);
+ }
+
+ ///
+ /// Verifies that if the AeadAes256CbcHmac256EncryptionKey is
+ /// constructed with a valid root key, it successfully generates
+ /// an encryption key, MAC key and IV key.
+ ///
+ [Fact]
+ public void Constructor_SucceedsOnValidSize()
+ {
+ byte[] validRootKey = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+ ];
+ AeadAes256CbcHmac256EncryptionKey encryptionKey = new(validRootKey);
+
+ Assert.Equal(AeadAes256CbcHmac256EncryptionKey.KeySizeInBytes, encryptionKey.EncryptionKey.Length);
+ Assert.Equal(AeadAes256CbcHmac256EncryptionKey.KeySizeInBytes, encryptionKey.MacKey.Length);
+ Assert.Equal(AeadAes256CbcHmac256EncryptionKey.KeySizeInBytes, encryptionKey.IvKey.Length);
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256FactoryTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256FactoryTest.cs
new file mode 100644
index 0000000000..5a5cc12c51
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256FactoryTest.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
+using System;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.UnitTests.AlwaysEncrypted;
+
+///
+/// Unit tests to verify that AeadAes256CbcHmac256Factory's
+/// caching and validation logic behave correctly.
+///
+public class AeadAes256CbcHmac256FactoryTest
+{
+ ///
+ /// Verifies that if called with an invalid encryption type,
+ /// Create will throw.
+ ///
+ [Fact]
+ public void InvalidEncryptionType_Throws()
+ {
+ byte[] dummySymmetricKeyMaterial = [0x00];
+ SymmetricKey symmetricKey = new(dummySymmetricKeyMaterial);
+ Action createEncryptionAlgorithm = () =>
+ AeadAes256CbcHmac256Factory.Instance.Create(symmetricKey, (EncryptionType)0xFF, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+
+ Assert.Throws(createEncryptionAlgorithm);
+ }
+
+ ///
+ /// Verifies that if called twice with the same root key,
+ /// Create will return the same algorithm instance.
+ ///
+ [Fact]
+ public void MultipleCreateCalls_ReturnCachedAlgorithm()
+ {
+ byte[] validFirstRootKeyMaterial = [
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+ ];
+ byte[] validSecondRootKeyMaterial = [
+ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
+ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
+ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
+ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01
+ ];
+ SymmetricKey firstRootKey = new(validFirstRootKeyMaterial);
+ SymmetricKey secondRootKey = new(validSecondRootKeyMaterial);
+
+ SqlClientEncryptionAlgorithm initialFirstAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create(firstRootKey, EncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ SqlClientEncryptionAlgorithm cachedFirstAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create(firstRootKey, EncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+ SqlClientEncryptionAlgorithm initialSecondAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create(secondRootKey, EncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
+
+ Assert.Equal(initialFirstAlgorithm, cachedFirstAlgorithm);
+ Assert.NotEqual(initialFirstAlgorithm, initialSecondAlgorithm);
+
+ Assert.IsType(initialFirstAlgorithm);
+ Assert.IsType(initialSecondAlgorithm);
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryListTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryListTest.cs
new file mode 100644
index 0000000000..15b3b20d55
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryListTest.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
+using System;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.UnitTests.AlwaysEncrypted;
+
+///
+/// Unit tests to verify the validation logic within EncryptionAlgorithmFactoryList.
+///
+public class EncryptionAlgorithmFactoryListTest
+{
+ ///
+ /// Validates that if an unknown Always Encrypted algorithm name is specified,
+ /// GetAlgorithm throws.
+ ///
+ [Fact]
+ public void GetAlgorithmWithInvalidAlgorithm_Throws()
+ {
+ const string InvalidAlgorithmName = nameof(EncryptionAlgorithmFactoryListTest);
+
+ byte[] dummySymmetricKeyMaterial = [0x00];
+ SymmetricKey dummySymmetricKey = new(dummySymmetricKeyMaterial);
+ Action getAlgorithm = () => EncryptionAlgorithmFactoryList.GetAlgorithm(dummySymmetricKey, 0x01, InvalidAlgorithmName, out _);
+
+ ArgumentException thrownException = Assert.Throws(getAlgorithm);
+
+ Assert.Contains(InvalidAlgorithmName, thrownException.Message);
+ Assert.Contains(EncryptionAlgorithmFactoryList.RegisteredCipherAlgorithmNames, thrownException.Message);
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyTest.cs
new file mode 100644
index 0000000000..95939a7f6f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SymmetricKeyTest.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
+using System;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.UnitTests.AlwaysEncrypted;
+
+///
+/// Unit tests to verify that the SymmetricKey wrapper class
+/// and its validation logic behave correctly.
+///
+public class SymmetricKeyTest
+{
+ ///
+ /// Verifies that the wrapper class wraps the key material passed
+ /// to it, rather than copies it.
+ ///
+ [Fact]
+ public void Constructor_WrapsByteArray()
+ {
+ byte[] dummySymmetricKeyMaterial = [0x00];
+ SymmetricKey symmetricKey = new(dummySymmetricKeyMaterial);
+
+ Assert.Same(dummySymmetricKeyMaterial, symmetricKey.RootKey);
+ }
+
+ ///
+ /// Verifies that the wrapper class throws an exception when passed
+ /// a null or a zero-length array.
+ ///
+ [Fact]
+ public void Constructor_ThrowsOnNullOrEmptyArray()
+ {
+ Action createNullArray = () => new SymmetricKey(rootKey: null);
+ Action createEmptyArray = () => new SymmetricKey(rootKey: []);
+
+ Assert.Throws(createNullArray);
+ Assert.Throws(createEmptyArray);
+ }
+}