ASP.NET 和 ASP.NET Core 中使用 Azure Key Vault 始终加密的完整教程
如何使用 MSSQL 加密列功能和存储在 Azure Key Vault 中的加密密钥来使用 Always Encrypted。
引言
尽管该主题有所有可用的 Microsoft 文档,并且违背常识,但 AzureKeyVaultProvider
确实需要通过手动插入到您的项目中的代码来注册。
为什么是常识?原因如下:
- 使用 Azure Key Vault (AKV) 是保护证书的默认方法。
- Web App 托管在 Azure 中。
- 连接字符串包含 Always Encrypted 属性。
- 任何 Azure 帐户都有预定义的默认 Active Directory。
- Web App 可以激活为默认 AD 用户,并被授予 AKV 权限。
然而,这不仅默认不会发生,而且 MS 文档和 MS 支持都没有提供统一的解决方案。
行动计划
最终,在克服了 Microsoft 在此特定主题上的糟糕支持服务后,我们设法自行调试了正确的方法,并使其遵循以下三个强制步骤:
- 配置您的 Azure 帐户
- 创建 Azure Key Vault (AKV) 存储桶(或使用现有的)
- 为您的 Web App 创建专用的 Azure Active Directory (AD)
- 为该用户授予额外的加密权限。
- 升级您的 .NET 项目
- 向连接字符串添加属性。
- 添加
AzureKeyVaultProvider
NuGet 程序包。 - 将注册代码添加到项目启动例程。
- 为 Always Encrypt 设置您的数据库
- 使用列加密向导。
- 配置数据库连接以读取加密数据。
配置您的 Azure 帐户
在这里有三件事需要您注意:使用此 快速入门教程 创建 AKV(推荐)或使用现有的 AKV,然后将您的 Web 应用注册为 Active Directory 用户,并授予该用户对该 AKV 的适当权限。
在配置 Active Directory 时,请区分 AD 用户帐户和应用程序注册,并使用后者,如下图所示:
有趣的是,并且与 MS 支持的建议相反,您实际上需要创建一个完全独立的 AD 用户,而不是激活您的 Web 应用作为 AD 用户并使用其 Principal ID,并将其配置为具有适当的 AKV 访问权限。创建时,请确保复制其应用程序(客户端)ID和(客户端)密钥值(而不是密钥 ID),以便稍后在过程中使用,如下所示:
准备好 AD 用户数据后,导航回 AKV 并通过配置新的访问策略来配置用户具有适当的权限。
重要!! MS 文档中另一个被错误传播的建议是使用预定义的权限模板密钥、机密和证书管理,如下图所示:
实际上,您还需要加密操作,如下图所示:
升级您的 .NET 项目
A. 您首先需要更新连接字符串
转到您的 .config 文件并添加以下属性:
Column Encryption Setting=enabled;
重要!! 如果您正在为不同目的使用多个连接字符串,并且在每个连接字符串上都使用列加密,请将此属性添加到每个连接字符串!!!
对于高级编码人员,Microsoft 允许您实现自己的 自定义加密机制。
B. 第二步是添加 AzureKeyVaultProvider NuGet 程序包
重要!! 在 .NET 中,处理数据有两种不同的方法,每种方法来自不同的命名空间并使用一组单独的(但相似的)类:
System.Data.SqlClient
(SDS) 和Microsoft.Data.SqlClient
(MDS)
它们各自使用不同的 NuGet 程序包来使用存储在 AKV 中的 Always Encrypted 密钥来解密 SQL 数据。
C. 添加仅在项目启动时执行的代码
您在此处执行 AZURE_KEY_VAULT
提供程序名称的注册 - 该名称的缺失会导致下面的臭名昭著的异常:
Failed to decrypt a column encryption key. Invalid key store provider name:
'AZURE_KEY_VAULT'. A key store provider name must denote either a system
key store provider or a registered custom key store provider.
Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE',
'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered)
custom key store provider names are: . Please verify key store provider
information in column master key definitions in the database, and verify
all custom key store providers used in your application are registered properly.
重要!! AKV 存储提供程序注册代码只能执行一次,因此:如果您使用的是.NET MVC,请将此代码添加到 Global.asax 中,即 Application_Start
事件;如果您使用的是.NET MVC Core,请将此代码添加到 Startup.cs 类中。
重要!! 如果您的代码同时使用 SDS 和 MDS,您需要为两者都注册 AKV 存储提供程序。仅为一个注册是不够的!!
接下来,我们将假设 Entity Framework 传统上使用 (SDS) 命名空间,而 ADO Factory 使用 (MDS)。因此,后续的表示法将是:
- 通过调用
InitializeAzureKeyVaultProvider4Ado
方法为 ADO 用途注册 AKV 存储。 - 通过调用
InitializeAzureKeyVaultProvider4Ef
方法为 EF 用途注册 AKV 存储。
如果您想同时使用这两种情况,请按照以下三个步骤进行:
步骤 1
隔离每个库引用集,相对 SDS (EF) 和 MDS (ADO) 情况。
using AuthenticationResult =
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult;
using ClientCredential =
Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential;
using SqlColumnEncryptionAzureKeyVaultProviderAdo =
Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.
SqlColumnEncryptionAzureKeyVaultProvider;
using SqlColumnEncryptionAzureKeyVaultProviderEf =
Microsoft.SqlServer.Management.AlwaysEncrypted.
AzureKeyVaultProvider.SqlColumnEncryptionAzureKeyVaultProvider;
using SqlColumnEncryptionKeyStoreProviderAdo =
Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider;
using SqlColumnEncryptionKeyStoreProviderEf =
System.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider;
using SqlConnectionAdo = Microsoft.Data.SqlClient.SqlConnection;
using SqlConnectionEf = System.Data.SqlClient.SqlConnection;
第二步
在应用程序注册过程中(即应用程序(客户端)ID 和(客户端)密钥值)定义的 Azure Active Directory 凭据初始化。
static readonly string s_clientId = "---";
static readonly string s_clientSecret = "---";
private static ClientCredential _clientCredential;
步骤 3
添加前面提到的两个单独的初始化方法 InitializeAzureKeyVaultProvider4Ef
和 InitializeAzureKeyVaultProvider4Ado
。
对于 EF
private static void InitializeAzureKeyVaultProvider4Ef()
{
_clientCredential = new ClientCredential(s_clientId, s_clientSecret);
SqlColumnEncryptionAzureKeyVaultProviderEf azureKeyVaultProvider =
new SqlColumnEncryptionAzureKeyVaultProviderEf(GetToken);
Dictionary<string, SqlColumnEncryptionKeyStoreProviderEf> providers =
new Dictionary<string, SqlColumnEncryptionKeyStoreProviderEf>();
providers.Add(SqlColumnEncryptionAzureKeyVaultProviderEf.ProviderName,
azureKeyVaultProvider);
SqlConnectionEf.RegisterColumnEncryptionKeyStoreProviders(providers);
}
对于 ADO
private static void InitializeAzureKeyVaultProvider4Ado()
{
_clientCredential = new ClientCredential(s_clientId, s_clientSecret);
SqlColumnEncryptionAzureKeyVaultProviderAdo azureKeyVaultProviderAdo =
new SqlColumnEncryptionAzureKeyVaultProviderAdo(GetToken);
Dictionary<string, SqlColumnEncryptionKeyStoreProviderAdo> providers =
new Dictionary<string, SqlColumnEncryptionKeyStoreProviderAdo>();
providers.Add(SqlColumnEncryptionAzureKeyVaultProviderAdo.ProviderName,
azureKeyVaultProviderAdo);
SqlConnectionAdo.RegisterColumnEncryptionKeyStoreProviders(providers);
}
根据您的项目遗留状态,您实际上需要使用两种不同的实现方案。但是,最快捷的选择是将 MDS 从其最新版本(3.0.0)降级到 1.2.0,因为它通过使用相同的回调函数来获取身份验证令牌,从而为您提供与 SDS 的线性一致性。
对于 v.1.0.0 到 v.1.2.0 范围内的任何版本,请使用此回调:
private async static Task<string> GetToken
(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
AuthenticationResult result = await authContext.AcquireTokenAsync
(resource, _clientCredential);
if (result == null)
throw new InvalidOperationException("Failed to obtain the access token");
return result.AccessToken;
}
对于 v.2.0.0 及以上版本的任何版本,您可以在有关该主题的MS 文档中找到其广泛的解决方案,这要归功于 SqlClient
团队在其公共 Git 存储库中非常活跃。
设置您的数据库以进行 Always Encrypt
使用列加密功能,可以通过 SSMS 向导(从表右键上下文菜单触发,推荐)或通过 MS 描述的代码示例。
对于所有常见情况,我建议使用本教程中描述并在下图中显示的 SSMS 向导。
重要!! 使用此列加密功能时,它会更改选定列的排序规则。
重要!! 这种方法还为您提供了加密列的实时解密,因此您可以来回切换 - 但是,解密列不会更改其排序规则,因此某些查询操作(如文本比较)将无法正常工作。
但是,如果您想处理自定义方法或想掌握该过程,请遵循此链接。
回到日常的调试和维护过程,您需要配置您的数据库连接以以明文形式读取加密数据。
显然,加密数据后应用程序问题并不会消失,因此,出于维护目的,如果您在连接到服务器表单的始终加密选项卡中勾选启用始终加密选项,您仍然可以调试明文视图数据,该选项在上述教程中也有描述,并在下图中可见。
重要!! 无需担心开发人员可以随意访问明文数据!!在执行连接时,SSMS 需要一个 Azure 用户凭据,该凭据已事先被设置为具有对特定 KeyVault
的适当访问权限。因此,安全控制已到位。
结论
使用 Microsoft 的列加密功能显而易见的优势是即时的:
- 您拥有一个即时可逆的机制。
- 尽管对其他人来说是加密的,但您仍然可以读取明文。
- 您可以随时更改加密密钥。
- 默认情况下与 Azure Key Vault 配合使用。
- 默认情况下,它与 Active Directory 集成。
我知道许多开发人员仍在自己开发的列加密机制 - 我曾经就是其中之一。我也理解为什么他们仍然使用相同的方法,因为他们在文档中迷失了方向 - 但那已经是过去式了,不是吗?:)
历史
- 2023 年 2 月 21 日:初始版本