ASP.NET Core 中的密钥管理扩展性

在阅读本部分之前,请阅读密钥管理部分,因为这部分介绍了这些 API 背后的一些基本概念。

警告:对于多个调用方,实现以下任何接口的类型都应该是线程安全的。

密钥

IKey 接口是加密系统中密钥的基本表示形式。 此处使用的术语“密钥”为抽象意义,而不是字面意义的“加密密钥材料”。 密钥具有以下属性:

  • 激活、创建和到期日期

  • 吊销状态

  • 密钥标识符 (GUID)

此外,IKey 将公开 CreateEncryptor 方法,该方法可用于创建绑定到此密钥的 IAuthenticatedEncryptor 实例。

此外,IKey 将公开 CreateEncryptorInstance 方法,该方法可用于创建绑定到此密钥的 IAuthenticatedEncryptor 实例。

注意

没有用于从 IKey 实例中检索原始加密材料的 API。

IKeyManager

IKeyManager 接口表示负责常规密钥存储、检索和操作的对象。 该接口公开三个高级操作:

  • 创建新密钥并将其持久保存到存储中。

  • 从存储获取所有密钥。

  • 撤销一个或多个密钥并将撤销信息持久保存到存储中。

警告

编写 IKeyManager 是非常高级的任务,大多数开发人员都不应尝试。 相反,大多数开发人员应充分利用 XmlKeyManager 类的功能。

XmlKeyManager

XmlKeyManager 类型是 IKeyManager 的内置具体实现。 它提供多种有用功能,包括密钥托管和密钥静态加密。 此系统中的密钥表示为 XML 元素(具体说来就是 XElement)。

XmlKeyManager 在完成其任务的过程中依赖于多个其他组件:

  • AlgorithmConfiguration,指示新密钥使用的算法。

  • IXmlRepository,控制密钥在存储中的持久存储位置。

  • IXmlEncryptor [可选],可允许 rest 密钥加密。

  • IKeyEscrowSink [可选],提供密钥托管服务。

  • IXmlRepository,控制密钥在存储中的持久存储位置。

  • IXmlEncryptor [可选],可允许 rest 密钥加密。

  • IKeyEscrowSink [可选],提供密钥托管服务。

下面是高级关系图,指示这些组件如何在 XmlKeyManager 中连接在一起。

密钥创建

密钥创建/CreateNewKey

CreateNewKey 的实现中,AlgorithmConfiguration 组件用于创建一个唯一的 IAuthenticatedEncryptorDescriptor(之后序列化为 XML)。 如果存在密钥托管接收器,则会向该接收器提供原始(未加密的)XML 以进行长期存储。 然后将对该未加密的 XML 运行 IXmlEncryptor(如果需要)以生成加密的 XML 文档。 此加密文档将通过 IXmlRepository 持久保存到长期存储中。 (如果未配置 IXmlEncryptor,则会将未加密的文档持久保存在 IXmlRepository 中。)

密钥检索

密钥创建

密钥创建/CreateNewKey

CreateNewKey 的实现中,IAuthenticatedEncryptorConfiguration 组件用于创建一个唯一的 IAuthenticatedEncryptorDescriptor(之后序列化为 XML)。 如果存在密钥托管接收器,则会向该接收器提供原始(未加密的)XML 以进行长期存储。 然后将对该未加密的 XML 运行 IXmlEncryptor(如果需要)以生成加密的 XML 文档。 此加密文档将通过 IXmlRepository 持久保存到长期存储中。 (如果未配置 IXmlEncryptor,则会将未加密的文档持久保存在 IXmlRepository 中。)

密钥检索

密钥检索/GetAllKeys

GetAllKeys 的实现中,将从基础 IXmlRepository 中读取表示密钥和撤销的 XML 文档。 如果这些文档已加密,系统会自动将其解密。 XmlKeyManager 创建相应的 IAuthenticatedEncryptorDescriptorDeserializer 实例,以将文档反序列化回 IAuthenticatedEncryptorDescriptor 实例,然后将其包装在各个 IKey 实例中。 此 IKey 实例集合将返回到调用方。

有关特定 XML 元素的详细信息,请参阅密钥存储格式文档

IXmlRepository

IXmlRepository 接口表示可将 XML 持久保存到后备存储并从中检索 XML 的类型。 该接口公开两个 API:

  • GetAllElementsIReadOnlyCollection<XElement>

  • StoreElement(XElement element, string friendlyName)

IXmlRepository 的实现不需要分析通过它们传递的 XML。 它们应将 XML 文档视为不透明,并使更高的层来负责生成和分析文档。

有四个可实现 IXmlRepository 的内置具体类型:

有关详细信息,请参阅密钥存储提供程序文档

使用不同的后备存储时,应注册自定义 IXmlRepository(例如,Azure 表存储)。

若要更改应用程序范围的默认存储库,请注册自定义 IXmlRepository 实例:

services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());

IXmlEncryptor

IXmlEncryptor 接口表示可加密纯文本 XML 元素的类型。 该接口公开一个 API:

  • Encrypt(XElement plaintextElement) : EncryptedXmlInfo

如果序列化的 IAuthenticatedEncryptorDescriptor 包含任何标记为“需要加密”的元素,则 XmlKeyManager 将通过已配置的 IXmlEncryptorEncrypt 方法运行这些元素,并将加密元素而不是纯文本元素持久保存到 IXmlRepositoryEncrypt 方法的输出是一个 EncryptedXmlInfo 对象。 此对象是一个包装器,其中包含生成的 XElement 以及和表示 IXmlDecryptor 的类型,该类型可用于对相应元素进行解密。

有四个可实现 IXmlEncryptor 的内置具体类型:

有关详细信息,请参阅密钥 rest 加密文档

若要更改应用程序范围的默认密钥 rest 加密机制,请注册自定义 IXmlEncryptor 实例:

services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());

IXmlDecryptor

IXmlDecryptor 接口表示一种类型,该类型知道如何对通过 IXmlEncryptor 进行加密的 XElement 进行解密。 该接口公开一个 API:

  • Decrypt(XElement encryptedElement) : XElement

Decrypt 方法可撤消 IXmlEncryptor.Encrypt 执行的加密。 通常,每个具体 IXmlEncryptor 实现都具有相应的具体 IXmlDecryptor 实现。

实现 IXmlDecryptor 的类型应具有以下两个公共构造函数之一:

  • .ctor(IServiceProvider)
  • .ctor()

注意

传递给构造函数的 IServiceProvider 可能为 NULL。

IKeyEscrowSink

IKeyEscrowSink 接口表示可以执行敏感信息托管的类型。 请记住,序列化描述符可能包含敏感信息(如加密材料),这就是需要引入 IXmlEncryptor 类型的最初原因。 但是,有时会发生意外,密钥环可能会被删除或损坏。

托管接口可提供紧急安全门,允许在原始序列化 XML 被任何已配置的 IXmlEncryptor 转换之前对其进行访问。 该接口公开一个 API:

  • Store(Guid keyId, XElement element)

此时由 IKeyEscrowSink 以符合企业政策的安全方式来处理所提供的元素。 对于托管接收器,一种可能的实现是使用已知的公司 x.509 证书(证书的私钥已托管)对 XML 元素进行加密;CertificateXmlEncryptor 类型可用于此情况。 IKeyEscrowSink 实现还负责妥善保存提供的元素。

默认情况下不会启用任何托管机制,但服务器管理员可对此进行全局配置。 还可通过 IDataProtectionBuilder.AddKeyEscrowSink 方法以编程方式进行此配置,如下面的示例所示。 AddKeyEscrowSink 方法重载可对 IServiceCollection.AddSingletonIServiceCollection.AddInstance 重载进行镜像操作,因为 IKeyEscrowSink 实例应为单一实例。 如果注册了多个 IKeyEscrowSink 实例,则密钥生成过程中将调用每个实例,以便可以将密钥同时托管到多个机制中。

没有可从 IKeyEscrowSink 实例读取材料的 API。 这与托管机制的设计理论一致:它旨在使密钥材料可供受信任的颁发机构访问,因为该应用程序本身不是受信任的颁发机构,所以它不应访问其自己的托管材料。

下面的示例代码演示如何创建和注册 IKeyEscrowSink示例中的密钥已进行托管,以便只有“CONTOSODomain Admins”的成员才能恢复它们。

注意

若要运行此示例,必须使用已加入域的 Windows 8/Windows Server 2012 计算机,并且域控制器必须为 Windows Server 2012 或更高版本。

using System;
using System.IO;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
            .ProtectKeysWithDpapi()
            .AddKeyEscrowSink(sp => new MyKeyEscrowSink(sp));
        var services = serviceCollection.BuildServiceProvider();

        // get a reference to the key manager and force a new key to be generated
        Console.WriteLine("Generating new key...");
        var keyManager = services.GetService<IKeyManager>();
        keyManager.CreateNewKey(
            activationDate: DateTimeOffset.Now,
            expirationDate: DateTimeOffset.Now.AddDays(7));
    }

    // A key escrow sink where keys are escrowed such that they
    // can be read by members of the CONTOSO\Domain Admins group.
    private class MyKeyEscrowSink : IKeyEscrowSink
    {
        private readonly IXmlEncryptor _escrowEncryptor;

        public MyKeyEscrowSink(IServiceProvider services)
        {
            // Assuming I'm on a machine that's a member of the CONTOSO
            // domain, I can use the Domain Admins SID to generate an
            // encrypted payload that only they can read. Sample SID from
            // https://technet.microsoft.com/library/cc778824(v=ws.10).aspx.
            _escrowEncryptor = new DpapiNGXmlEncryptor(
                "SID=S-1-5-21-1004336348-1177238915-682003330-512",
                DpapiNGProtectionDescriptorFlags.None,
                new LoggerFactory());
        }

        public void Store(Guid keyId, XElement element)
        {
            // Encrypt the key element to the escrow encryptor.
            var encryptedXmlInfo = _escrowEncryptor.Encrypt(element);

            // A real implementation would save the escrowed key to a
            // write-only file share or some other stable storage, but
            // in this sample we'll just write it out to the console.
            Console.WriteLine($"Escrowing key {keyId}");
            Console.WriteLine(encryptedXmlInfo.EncryptedElement);

            // Note: We cannot read the escrowed key material ourselves.
            // We need to get a member of CONTOSO\Domain Admins to read
            // it for us in the event we need to recover it.
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Generating new key...
 * Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
 * <encryptedKey>
 *   <!-- This key is encrypted with Windows DPAPI-NG. -->
 *   <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
 *   <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
 * </encryptedKey>
 */