配置 ASP.NET Core 数据保护

初始化后,数据保护系统会基于操作环境应用默认设置。 这些设置适用于在单台计算机上运行的应用。 但是,在某些情况下,开发人员可能要更改默认设置:

  • 应用分布在多台计算机上。
  • 出于合规性原因。

对于这些情形,数据保护系统提供了丰富的配置 API。

警告

与配置文件类似,应使用适当的权限保护数据保护密钥环。 可以选择对密钥进行 rest 加密,但这不会阻止网络攻击者创建新密钥。 因此,应用的安全性会受到影响。 使用数据保护配置的存储位置应该将其访问权限限制为应用本身,这与保护配置文件的方式类似。 例如,如果选择将密钥环存储在磁盘上,请使用文件系统权限。 确保用于运行 Web 应用的 identity 拥有对该目录的读取、写入和创建访问权限。 如果使用 Azure Blob 存储,则只有 Web 应用才应能够在 Blob 存储中读取、写入或创建新条目等。

扩展方法 AddDataProtection 会返回 IDataProtectionBuilderIDataProtectionBuilder 会公开扩展方法,可以将这些方法链接在一起来配置数据保护选项。

注意

本文是为一个在 docker 容器中运行的应用编写的。 在 docker 容器中,该应用始终具有相同的路径,因此,也具有相同的应用程序鉴别器。 对于需要在多个环境(例如本地和已部署环境)中运行的应用,必须设置环境的默认应用程序鉴别器。 在多个环境中运行应用超出了本文的介绍范围。

本文中使用的数据保护扩展需要以下 NuGet 包:

ProtectKeysWithAzureKeyVault

使用 CLI 登录 Azure,例如:

az login

若要使用 Azure Key Vault 管理密钥,请使用 Program.cs 中的 ProtectKeysWithAzureKeyVault 配置系统。 blobUriWithSasToken 是应用于存储密钥文件的完整 URI。 此 URI 必须包含 SAS 令牌作为查询字符串参数:

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

若要使应用与 KeyVault 进行通信并使用它向自己授权,必须添加 Azure.Identity 包。

设置密钥环存储位置设置(例如 PersistKeysToAzureBlobStorage)。 必须设置该位置,因为调用 ProtectKeysWithAzureKeyVault 会实现禁用自动数据保护设置(包括密钥环存储位置)的 IXmlEncryptor。 上面的示例使用 Azure Blob 存储来保持密钥环。 有关详细信息,请参阅密钥存储提供程序:Azure 存储。 还可以使用 PersistKeysToFileSystem 在本地保持密钥环。

keyIdentifier 是用于密钥加密的密钥保管库密钥标识符。 例如,在 contosokeyvault 的名为 dataprotection 的密钥保管库中创建的密钥会具有密钥标识符 https://contosokeyvault.vault.azure.net/keys/dataprotection/。 向应用提供对密钥保管库的“获取”、“解包密钥”和“包装密钥”权限。

ProtectKeysWithAzureKeyVault 重载:

如果应用使用较旧 Azure 包(Microsoft.AspNetCore.DataProtection.AzureStorage 和 Microsoft.AspNetCore.DataProtection.AzureKeyVault),则建议删除这些引用并升级到 Azure.Extensions.AspNetCore.DataProtection.BlobsAzure.Extensions.AspNetCore.DataProtection.Keys。 这些包是提供新更新的位置,可解决较旧包的一些关键安全性和稳定性问题。

builder.Services.AddDataProtection()
    // This blob must already exist before the application is run
    .PersistKeysToAzureBlobStorage("<storageAccountConnectionString", "<containerName>", "<blobName>")
    // Removing this line below for an initial run will ensure the file is created correctly
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

PersistKeysToFileSystem

若要将密钥存储在 UNC 共享(而不是 %LOCALAPPDATA% 默认位置),请使用 PersistKeysToFileSystemPersistKeysToFileSystem 配置系统:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));

警告

如果更改密钥保存位置,系统不再自动对密钥进行静态加密,因为它不知道 DPAPI 是否为合适的加密机制。

PersistKeysToDbContext

若要使用 EntityFramework 将密钥存储在数据库中,请使用 Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 包配置系统:

builder.Services.AddDataProtection()
    .PersistKeysToDbContext<SampleDbContext>();

上面的代码将密钥存储在配置的数据库中。 所使用的数据库上下文必须实现 IDataProtectionKeyContextIDataProtectionKeyContext 会公开属性 DataProtectionKeys

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } = null!;

此属性表示在其中存储密钥的表。 手动或使用 DbContext 迁移创建该表。 有关详细信息,请参阅 DataProtectionKey

ProtectKeysWith*

可以通过调用任何 ProtectKeysWith* 配置 API 将系统配置为对密钥进行 rest 保护。 请考虑下面的示例,它将密钥存储在 UNC 共享上,并使用特定 X.509 证书对这些密钥进行 rest 加密:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(builder.Configuration["CertificateThumbprint"]);

可以提供 X509Certificate2ProtectKeysWithCertificate,例如从文件加载的证书:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]));

有关内置密钥加密机制的更多示例和讨论,请参阅 Rest 密钥加密

UnprotectKeysWithAnyCertificate

可以通过将 X509Certificate2 证书数组与 UnprotectKeysWithAnyCertificate 结合使用,来轮换证书并对密钥进行 rest 解密:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]))
    .UnprotectKeysWithAnyCertificate(
        new X509Certificate2("certificate_1.pfx", builder.Configuration["CertificatePassword_1"]),
        new X509Certificate2("certificate_2.pfx", builder.Configuration["CertificatePassword_2"]));

SetDefaultKeyLifetime

若要将系统配置为使用 14 天(而不是默认 90 天)的密钥生存期,请使用 SetDefaultKeyLifetime

builder.Services.AddDataProtection()
    .SetDefaultKeyLifetime(TimeSpan.FromDays(14));

SetApplicationName

默认情况下,数据保护系统基于内容根路径将应用相互隔离,即使它们共享相同的物理密钥存储库也是如此。 此隔离机制可防止应用了解彼此的受保护有效负载。

若要在应用间共享受保护有效负载,请执行以下操作:

builder.Services.AddDataProtection()
    .SetApplicationName("<sharedApplicationName>");

SetApplicationName 在内部设置 DataProtectionOptions.ApplicationDiscriminator。 出于故障排除目的,可以使用在 Program.cs 中构建 WebApplication 之后放置的以下代码来记录框架分配给鉴别器的值:

var discriminator = app.Services.GetRequiredService<IOptions<DataProtectionOptions>>()
    .Value.ApplicationDiscriminator;
app.Logger.LogInformation("ApplicationDiscriminator: {ApplicationDiscriminator}", discriminator);

有关如何使用鉴别器的详细信息,请参阅本文后面的以下部分:

警告

在 .NET 6 中,WebApplicationBuilder 会将内容根路径规范化以 DirectorySeparatorChar 结尾。 例如,在 Windows 上,内容根路径以 \ 结尾,在 Linux 上则以 / 结尾。 其他主机不会规范化路径。 大多数从 HostBuilderWebHostBuilder 迁移的应用不会共享相同的应用名称,因为它们没有终止 DirectorySeparatorChar。 若要解决此问题,请删除目录分隔符并手动设置应用名称,如以下代码所示:

using Microsoft.AspNetCore.DataProtection;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var trimmedContentRootPath = builder.Environment.ContentRootPath.TrimEnd(Path.DirectorySeparatorChar);
builder.Services.AddDataProtection()
 .SetApplicationName(trimmedContentRootPath);
var app = builder.Build();

app.MapGet("/", () => Assembly.GetEntryAssembly()!.GetName().Name);

app.Run();

DisableAutomaticKeyGeneration

你可能会遇到这样的情况:不希望应用自动滚动更新密钥(创建新密钥),因为它们接近过期。 这种情况的一个示例可能是采用主/辅助关系进行设置的应用,其中只有主应用才负责密钥管理问题,而辅助应用只是具有密钥环的只读视图。 可以通过使用 DisableAutomaticKeyGeneration 配置系统,将辅助应用配置为将密钥环视为只读:

builder.Services.AddDataProtection()
    .DisableAutomaticKeyGeneration();

按应用程序隔离

当数据保护系统由 ASP.NET Core 主机提供时,它会自动将应用彼此隔离,即使这些应用在相同的工作进程帐户下运行并且使用相同的主密钥材料也是如此。 这类似于 System.Web 的 <machineKey> 元素中的 IsolateApps 修饰符。

隔离机制的工作方式是将本地计算机上的每个应用都视为唯一的租户,因此,任何给定应用的根 IDataProtector 都会自动包含应用 ID 作为鉴别器 (ApplicationDiscriminator)。 应用的唯一 ID 是应用的物理路径:

  • 对于在 IIS 中托管的应用,唯一 ID 是应用的 IIS 物理路径。 如果应用在 Web 场环境中进行部署,则此值是稳定的(假定在 Web 场中的所有计算机间都以相似方式配置 IIS 环境)。
  • 对于在 Kestrel server 上运行的自承载应用,唯一 ID 是应用在磁盘上的物理路径。

唯一标识符的设计目的是在重置后(单个应用和计算机本身)仍保持活动状态。

此隔离机制假定应用不是恶意的。 恶意应用可能会始终影响在相同工作进程帐户下运行的任何其他应用。 在应用互相不受信任的共享宿主环境中,托管提供商应采取措施来确保应用之间的操作系统级隔离,包括分隔应用的基础密钥存储库。

如果数据保护系统不由 ASP.NET Core 主机提供(例如,如果通过 DataProtectionProvider 具体类型进行实例化),则默认情况下会禁用应用隔离。 禁用应用隔离后,只要提供合适的用途,由相同密钥材料支持的所有应用便可以共享有效负载。 若要在此环境中提供应用隔离,请对配置对象调用 SetApplicationName 方法,并为每个应用提供唯一的名称。

数据保护和应用隔离

对于应用隔离,请考虑以下几点:

  • 当多个应用指向相同的密钥存储库时,意图是让这些应用共享相同的主密钥材料。 开发数据保护时,假设共享密钥环的所有应用都可以访问该密钥环中的所有项目。 应用程序唯一标识符用于隔离从密钥环提供密钥派生的应用程序特定密钥。 它不需要项目级权限,例如 Azure KeyVault 提供的用于强制实施额外隔离的权限。 尝试项级权限会生成应用程序错误。 如果你不想依赖内置应用程序隔离,则应使用单独的密钥存储位置,而不是在应用程序之间共享。

  • 应用程序鉴别器 (ApplicationDiscriminator) 用于允许不同的应用共享相同的主密钥材料,但使其加密有效负载彼此不同。 若要使应用能够读取彼此的加密负载,它们必须具有相同的应用程序鉴别器(可通过调用 SetApplicationName 进行设置)。

  • 如果某个应用遭到入侵(例如通过 RCE 攻击),则可供该应用访问的所有主密钥材料也必须被视为遭到入侵,而不考虑其 rest 保护状态。 这意味着,如果两个应用指向相同的存储库,那么即使它们使用不同的应用鉴别器,一个应用遭到入侵在功能上等同于两者都遭到入侵。

    即使两个应用使用不同的密钥保护机制进行 rest 密钥保护,此“在功能上等同于两者都遭到入侵”子句也成立。 通常,这不是预期配置。 rest 保护机制旨在当网络攻击者获得存储库读取访问权限时提供保护。 获取存储库写入访问权限的网络攻击者(可能是因为他们获得了应用内的代码执行权限)可以将恶意密钥插入到存储中。 数据保护系统故意不为获得密钥存储库写入访问权限的网络攻击者提供保护。

  • 如果应用需要真正保持相互隔离,则它们应使用不同的密钥存储库。 这自然不属于“隔离”的定义。 如果应用全部对彼此的数据存储具有读取和写入访问权限,则应用未隔离

使用 UseCryptographicAlgorithms 更改算法

数据保护堆栈允许更改新生成的密钥所使用的默认算法。 执行此操作的最简单方法是从配置回调调用 UseCryptographicAlgorithms

builder.Services.AddDataProtection()
    .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

默认 EncryptionAlgorithm 为 AES-256-CBC,默认 ValidationAlgorithm 为 HMACSHA256。 系统管理员可以通过计算机范围策略设置默认策略,但是对 UseCryptographicAlgorithms 的显式调用会替代默认策略。

通过调用 UseCryptographicAlgorithms 可以从预定义内置列表指定所需算法。 无需担心算法的实现。 在以上方案中,如果在 Windows 上运行,则数据保护系统会尝试使用 AES 的 CNG 实现。 否则,它会回退到托管 System.Security.Cryptography.Aes 类。

可以通过调用 UseCustomCryptographicAlgorithms 手动指定实现。

提示

更改算法不会影响密钥环中的现有密钥。 它仅影响新生成的密钥。

指定自定义托管算法

若要指定自定义托管算法,请创建指向实现类型的 ManagedAuthenticatedEncryptorConfiguration 实例:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new ManagedAuthenticatedEncryptorConfiguration
    {
        // A type that subclasses SymmetricAlgorithm
        EncryptionAlgorithmType = typeof(Aes),

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // A type that subclasses KeyedHashAlgorithm
        ValidationAlgorithmType = typeof(HMACSHA256)
    });

通常,*Type 属性必须指向 SymmetricAlgorithmKeyedHashAlgorithm 的可实例化(通过公共无参数构造函数)具体实现,不过为了方便起见,系统对一些值(如 typeof(Aes))进行了特殊处理。

注意

SymmetricAlgorithm 必须具有 ≥ 128 位的密钥长度和 ≥ 64 位的块大小,并且必须支持具有 PKCS #7 填充的 CBC 模式加密。 KeyedHashAlgorithm 的摘要大小必须为 >= 128 位,并且它必须支持长度等于哈希算法摘要长度的密钥。 KeyedHashAlgorithm 并不严格要求是 HMAC。

指定自定义 Windows CNG 算法

若要使用具有 HMAC 验证的 CBC 模式加密来指定自定义 Windows CNG 算法,请创建包含算法信息的 CngCbcAuthenticatedEncryptorConfiguration 实例:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new CngCbcAuthenticatedEncryptorConfiguration
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // Passed to BCryptOpenAlgorithmProvider
        HashAlgorithm = "SHA256",
        HashAlgorithmProvider = null
    });

注意

symmetric 分组加密算法必须具有 >= 128 位的密钥长度和 >= 64 位的块大小,并且必须支持具有 PKCS #7 填充的 CBC 模式加密。 哈希算法的摘要大小必须为 >= 128 位,并且必须支持使用 BCRYPT_ALG_HANDLE_HMAC_FLAG 标志打开。 *Provider 属性可以设置为 null,以将默认提供程序用于指定算法。 有关详细新,请参阅 BCryptOpenAlgorithmProvider 文档。

若要使用具有验证的 Galois/Counter 模式加密来指定自定义 Windows CNG 算法,请创建包含算法信息的 CngGcmAuthenticatedEncryptorConfiguration 实例:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new CngGcmAuthenticatedEncryptorConfiguration
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256
    });

注意

symmetric 分组加密算法必须具有 >= 128 位的密钥长度和恰好 128 位的块大小,并且必须支持 GCM 加密。 可以将 EncryptionAlgorithmProvider 属性设置为 null,以将默认提供程序用于指定算法。 有关详细新,请参阅 BCryptOpenAlgorithmProvider 文档。

指定其他自定义算法

尽管不是作为第一类 API 公开,但数据保护系统的扩展性足以允许指定几乎任何类型的算法。 例如,可以保留硬件安全模块 (HSM) 中包含的所有密钥,并提供核心加密和解密例程的自定义实现。 有关详细信息,请参阅核心加密扩展性中的 IAuthenticatedEncryptor

在 Docker 容器中托管时保持密钥

Docker 容器中托管时,应在以下任一项中维护密钥:

使用 Redis 保持密钥

只应使用支持 Redis 数据持久性的 Redis 版本来存储密钥。 Azure Blob 存储是持久性的,可用于存储密钥。 有关详细信息,请参阅此 GitHub 问题

对 DataProtection 进行日志记录

启用 DataProtection 的 Information 级别日志记录可帮助诊断问题。 以下 appsettings.json 文件启用 DataProtection API 的信息日志记录:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.DataProtection": "Information"
    }
  },
  "AllowedHosts": "*"
}

有关日志记录的详细信息,请参阅 .NET Core 和 ASP.NET Core 中的日志记录

其他资源

初始化后,数据保护系统会基于操作环境应用默认设置。 这些设置适用于在单台计算机上运行的应用。 但是,在某些情况下,开发人员可能要更改默认设置:

  • 应用分布在多台计算机上。
  • 出于合规性原因。

对于这些情形,数据保护系统提供了丰富的配置 API。

警告

与配置文件类似,应使用适当的权限保护数据保护密钥环。 可以选择对密钥进行 rest 加密,但这不会阻止网络攻击者创建新密钥。 因此,应用的安全性会受到影响。 使用数据保护配置的存储位置应该将其访问权限限制为应用本身,这与保护配置文件的方式类似。 例如,如果选择将密钥环存储在磁盘上,请使用文件系统权限。 确保用于运行 Web 应用的 identity 拥有对该目录的读取、写入和创建访问权限。 如果使用 Azure Blob 存储,则只有 Web 应用才应能够在 Blob 存储中读取、写入或创建新条目等。

扩展方法 AddDataProtection 会返回 IDataProtectionBuilderIDataProtectionBuilder 会公开扩展方法,可以将这些方法链接在一起来配置数据保护选项。

本文中使用的数据保护扩展需要以下 NuGet 包:

ProtectKeysWithAzureKeyVault

使用 CLI 登录 Azure,例如:

az login

若要将密钥存储在 Azure Key Vault 中,请使用 Startup 类中的 ProtectKeysWithAzureKeyVault 配置系统。 blobUriWithSasToken 是应用于存储密钥文件的完整 URI。 此 URI 必须包含 SAS 令牌作为查询字符串参数:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
        .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
}

若要使应用与 KeyVault 进行通信并使用它向自己授权,必须添加 Azure.Identity 包。

设置密钥环存储位置设置(例如 PersistKeysToAzureBlobStorage)。 必须设置该位置,因为调用 ProtectKeysWithAzureKeyVault 会实现禁用自动数据保护设置(包括密钥环存储位置)的 IXmlEncryptor。 上面的示例使用 Azure Blob 存储来保持密钥环。 有关详细信息,请参阅密钥存储提供程序:Azure 存储。 还可以使用 PersistKeysToFileSystem 在本地保持密钥环。

keyIdentifier 是用于密钥加密的密钥保管库密钥标识符。 例如,在 contosokeyvault 的名为 dataprotection 的密钥保管库中创建的密钥会具有密钥标识符 https://contosokeyvault.vault.azure.net/keys/dataprotection/。 向应用提供对密钥保管库的“获取”、“解包密钥”和“包装密钥”权限。

ProtectKeysWithAzureKeyVault 重载:

如果应用使用较旧 Azure 包(Microsoft.AspNetCore.DataProtection.AzureStorage 和 Microsoft.AspNetCore.DataProtection.AzureKeyVault),则建议删除这些引用并升级到 Azure.Extensions.AspNetCore.DataProtection.BlobsAzure.Extensions.AspNetCore.DataProtection.Keys。 这些包是提供新更新的位置,可解决较旧包的一些关键安全性和稳定性问题。

services.AddDataProtection()
    //This blob must already exist before the application is run
    .PersistKeysToAzureBlobStorage("<storage account connection string", "<key store container name>", "<key store blob name>")
    //Removing this line below for an initial run will ensure the file is created correctly
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

PersistKeysToFileSystem

若要将密钥存储在 UNC 共享(而不是 %LOCALAPPDATA% 默认位置),请使用 PersistKeysToFileSystemPersistKeysToFileSystem 配置系统:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));
}

警告

如果更改密钥保存位置,系统不再自动对密钥进行静态加密,因为它不知道 DPAPI 是否为合适的加密机制。

PersistKeysToDbContext

若要使用 EntityFramework 将密钥存储在数据库中,请使用 Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 包配置系统:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToDbContext<DbContext>()
}

上面的代码将密钥存储在配置的数据库中。 所使用的数据库上下文必须实现 IDataProtectionKeyContextIDataProtectionKeyContext 会公开属性 DataProtectionKeys

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }

此属性表示在其中存储密钥的表。 手动或使用 DbContext 迁移创建该表。 有关详细信息,请参阅 DataProtectionKey

ProtectKeysWith*

可以通过调用任何 ProtectKeysWith* 配置 API 将系统配置为对密钥进行 rest 保护。 请考虑下面的示例,它将密钥存储在 UNC 共享上,并使用特定 X.509 证书对这些密钥进行 rest 加密:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(Configuration["Thumbprint"]);
}

可以提供 X509Certificate2ProtectKeysWithCertificate,例如从文件加载的证书:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(
            new X509Certificate2("certificate.pfx", Configuration["Thumbprint"]));
}

有关内置密钥加密机制的更多示例和讨论,请参阅 Rest 密钥加密

UnprotectKeysWithAnyCertificate

可以通过将 X509Certificate2 证书数组与 UnprotectKeysWithAnyCertificate 结合使用,来轮换证书并对密钥进行 rest 解密:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(
            new X509Certificate2("certificate.pfx", Configuration["MyPasswordKey"));
        .UnprotectKeysWithAnyCertificate(
            new X509Certificate2("certificate_old_1.pfx", Configuration["MyPasswordKey_1"]),
            new X509Certificate2("certificate_old_2.pfx", Configuration["MyPasswordKey_2"]));
}

SetDefaultKeyLifetime

若要将系统配置为使用 14 天(而不是默认 90 天)的密钥生存期,请使用 SetDefaultKeyLifetime

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}

SetApplicationName

默认情况下,数据保护系统基于内容根路径将应用相互隔离,即使它们共享相同的物理密钥存储库也是如此。 此隔离机制可防止应用了解彼此的受保护有效负载。

若要在应用间共享受保护有效负载,请执行以下操作:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetApplicationName("shared app name");
}

SetApplicationName 在内部设置 DataProtectionOptions.ApplicationDiscriminator。 有关如何使用鉴别器的详细信息,请参阅本文后面的以下部分:

DisableAutomaticKeyGeneration

你可能会遇到这样的情况:不希望应用自动滚动更新密钥(创建新密钥),因为它们接近过期。 这种情况的一个示例可能是采用主/辅助关系进行设置的应用,其中只有主应用才负责密钥管理问题,而辅助应用只是具有密钥环的只读视图。 可以通过使用 DisableAutomaticKeyGeneration 配置系统,将辅助应用配置为将密钥环视为只读:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .DisableAutomaticKeyGeneration();
}

按应用程序隔离

当数据保护系统由 ASP.NET Core 主机提供时,它会自动将应用彼此隔离,即使这些应用在相同的工作进程帐户下运行并且使用相同的主密钥材料也是如此。 这类似于 System.Web 的 <machineKey> 元素中的 IsolateApps 修饰符。

隔离机制的工作方式是将本地计算机上的每个应用都视为唯一的租户,因此,任何给定应用的根 IDataProtector 都会自动包含应用 ID 作为鉴别器 (ApplicationDiscriminator)。 应用的唯一 ID 是应用的物理路径:

  • 对于在 IIS 中托管的应用,唯一 ID 是应用的 IIS 物理路径。 如果应用在 Web 场环境中进行部署,则此值是稳定的(假定在 Web 场中的所有计算机间都以相似方式配置 IIS 环境)。
  • 对于在 Kestrel server 上运行的自承载应用,唯一 ID 是应用在磁盘上的物理路径。

唯一标识符的设计目的是在重置后(单个应用和计算机本身)仍保持活动状态。

此隔离机制假定应用不是恶意的。 恶意应用可能会始终影响在相同工作进程帐户下运行的任何其他应用。 在应用互相不受信任的共享宿主环境中,托管提供商应采取措施来确保应用之间的操作系统级隔离,包括分隔应用的基础密钥存储库。

如果数据保护系统不由 ASP.NET Core 主机提供(例如,如果通过 DataProtectionProvider 具体类型进行实例化),则默认情况下会禁用应用隔离。 禁用应用隔离后,只要提供合适的用途,由相同密钥材料支持的所有应用便可以共享有效负载。 若要在此环境中提供应用隔离,请对配置对象调用 SetApplicationName 方法,并为每个应用提供唯一的名称。

数据保护和应用隔离

对于应用隔离,请考虑以下几点:

  • 当多个应用指向相同的密钥存储库时,意图是让这些应用共享相同的主密钥材料。 开发数据保护时,假设共享密钥环的所有应用都可以访问该密钥环中的所有项目。 应用程序唯一标识符用于隔离从密钥环提供密钥派生的应用程序特定密钥。 它不需要项目级权限,例如 Azure KeyVault 提供的用于强制实施额外隔离的权限。 尝试项级权限会生成应用程序错误。 如果你不想依赖内置应用程序隔离,则应使用单独的密钥存储位置,而不是在应用程序之间共享。

  • 应用程序鉴别器 (ApplicationDiscriminator) 用于允许不同的应用共享相同的主密钥材料,但使其加密有效负载彼此不同。 若要使应用能够读取彼此的加密负载,它们必须具有相同的应用程序鉴别器(可通过调用 SetApplicationName 进行设置)。

  • 如果某个应用遭到入侵(例如通过 RCE 攻击),则可供该应用访问的所有主密钥材料也必须被视为遭到入侵,而不考虑其 rest 保护状态。 这意味着,如果两个应用指向相同的存储库,那么即使它们使用不同的应用鉴别器,一个应用遭到入侵在功能上等同于两者都遭到入侵。

    即使两个应用使用不同的密钥保护机制进行 rest 密钥保护,此“在功能上等同于两者都遭到入侵”子句也成立。 通常,这不是预期配置。 rest 保护机制旨在当网络攻击者获得存储库读取访问权限时提供保护。 获取存储库写入访问权限的网络攻击者(可能是因为他们获得了应用内的代码执行权限)可以将恶意密钥插入到存储中。 数据保护系统故意不为获得密钥存储库写入访问权限的网络攻击者提供保护。

  • 如果应用需要真正保持相互隔离,则它们应使用不同的密钥存储库。 这自然不属于“隔离”的定义。 如果应用全部对彼此的数据存储具有读取和写入访问权限,则应用未隔离

使用 UseCryptographicAlgorithms 更改算法

数据保护堆栈允许更改新生成的密钥所使用的默认算法。 执行此操作的最简单方法是从配置回调调用 UseCryptographicAlgorithms

services.AddDataProtection()
    .UseCryptographicAlgorithms(
        new AuthenticatedEncryptorConfiguration()
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

默认 EncryptionAlgorithm 为 AES-256-CBC,默认 ValidationAlgorithm 为 HMACSHA256。 系统管理员可以通过计算机范围策略设置默认策略,但是对 UseCryptographicAlgorithms 的显式调用会替代默认策略。

通过调用 UseCryptographicAlgorithms 可以从预定义内置列表指定所需算法。 无需担心算法的实现。 在以上方案中,如果在 Windows 上运行,则数据保护系统会尝试使用 AES 的 CNG 实现。 否则,它会回退到托管 System.Security.Cryptography.Aes 类。

可以通过调用 UseCustomCryptographicAlgorithms 手动指定实现。

提示

更改算法不会影响密钥环中的现有密钥。 它仅影响新生成的密钥。

指定自定义托管算法

若要指定自定义托管算法,请创建指向实现类型的 ManagedAuthenticatedEncryptorConfiguration 实例:

serviceCollection.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new ManagedAuthenticatedEncryptorConfiguration()
    {
        // A type that subclasses SymmetricAlgorithm
        EncryptionAlgorithmType = typeof(Aes),

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // A type that subclasses KeyedHashAlgorithm
        ValidationAlgorithmType = typeof(HMACSHA256)
    });

通常,*Type 属性必须指向 SymmetricAlgorithmKeyedHashAlgorithm 的可实例化(通过公共无参数构造函数)具体实现,不过为了方便起见,系统对一些值(如 typeof(Aes))进行了特殊处理。

注意

SymmetricAlgorithm 必须具有 ≥ 128 位的密钥长度和 ≥ 64 位的块大小,并且必须支持具有 PKCS #7 填充的 CBC 模式加密。 KeyedHashAlgorithm 的摘要大小必须为 >= 128 位,并且它必须支持长度等于哈希算法摘要长度的密钥。 KeyedHashAlgorithm 并不严格要求是 HMAC。

指定自定义 Windows CNG 算法

若要使用具有 HMAC 验证的 CBC 模式加密来指定自定义 Windows CNG 算法,请创建包含算法信息的 CngCbcAuthenticatedEncryptorConfiguration 实例:

services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new CngCbcAuthenticatedEncryptorConfiguration()
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // Passed to BCryptOpenAlgorithmProvider
        HashAlgorithm = "SHA256",
        HashAlgorithmProvider = null
    });

注意

symmetric 分组加密算法必须具有 >= 128 位的密钥长度和 >= 64 位的块大小,并且必须支持具有 PKCS #7 填充的 CBC 模式加密。 哈希算法的摘要大小必须为 >= 128 位,并且必须支持使用 BCRYPT_ALG_HANDLE_HMAC_FLAG 标志打开。 *Provider 属性可以设置为 null,以将默认提供程序用于指定算法。 有关详细新,请参阅 BCryptOpenAlgorithmProvider 文档。

若要使用具有验证的 Galois/Counter 模式加密来指定自定义 Windows CNG 算法,请创建包含算法信息的 CngGcmAuthenticatedEncryptorConfiguration 实例:

services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new CngGcmAuthenticatedEncryptorConfiguration()
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256
    });

注意

symmetric 分组加密算法必须具有 >= 128 位的密钥长度和恰好 128 位的块大小,并且必须支持 GCM 加密。 可以将 EncryptionAlgorithmProvider 属性设置为 null,以将默认提供程序用于指定算法。 有关详细新,请参阅 BCryptOpenAlgorithmProvider 文档。

指定其他自定义算法

尽管不是作为第一类 API 公开,但数据保护系统的扩展性足以允许指定几乎任何类型的算法。 例如,可以保留硬件安全模块 (HSM) 中包含的所有密钥,并提供核心加密和解密例程的自定义实现。 有关详细信息,请参阅核心加密扩展性中的 IAuthenticatedEncryptor

在 Docker 容器中托管时保持密钥

Docker 容器中托管时,应在以下任一项中维护密钥:

使用 Redis 保持密钥

只应使用支持 Redis 数据持久性的 Redis 版本来存储密钥。 Azure Blob 存储是持久性的,可用于存储密钥。 有关详细信息,请参阅此 GitHub 问题

对 DataProtection 进行日志记录

启用 DataProtection 的 Information 级别日志记录可帮助诊断问题。 以下 appsettings.json 文件启用 DataProtection API 的信息日志记录:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.DataProtection": "Information"
    }
  }
}

有关日志记录的详细信息,请参阅 .NET Core 和 ASP.NET Core 中的日志记录

其他资源