ASP.NET Core 的密钥管理可扩展性Key management extensibility in ASP.NET Core

本文内容

提示

阅读本部分之前,请阅读密钥管理部分,因为它说明了这些 api 背后的一些基本概念。

警告

实现以下接口的任何类型应该是线程安全的多个调用方。

密钥Key

IKey 接口是 cryptosystem 中密钥的基本表示形式。在抽象意义上,"加密密钥材料"字面意义上不在此处使用术语该键。一个键具有以下属性:

  • 激活、 创建和过期日期

  • 吊销状态

  • 密钥标识符 (GUID)

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

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

备注

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

IKeyManagerIKeyManager

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

  • 创建新的密钥,并将其保存到存储。

  • 从存储中获取所有键。

  • 撤消一个或多个密钥并保存到存储吊销信息。

警告

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

XmlKeyManagerXmlKeyManager

XmlKeyManager 类型是 IKeyManager的内置具体实现。它提供了几个有用的功能,包括密钥托管和静态密钥加密。此系统中的键表示为 XML 元素(特别是system.xml.linq.xelement>)。

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

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

  • IXmlRepository,控制在存储中保留密钥的位置。

  • IXmlEncryptor [可选],这允许静态加密密钥。

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

  • IXmlRepository,控制在存储中保留密钥的位置。

  • IXmlEncryptor [可选],这允许静态加密密钥。

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

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

创建密钥

密钥创建/CreateNewKey

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

密钥检索

创建密钥

密钥创建/CreateNewKey

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

密钥检索

密钥检索/GetAllKeys

GetAllKeys的实现中,将从基础 IXmlRepository读取表示键和吊销的 XML 文档。如果这些文档进行加密,系统将自动解密机密。XmlKeyManager 将创建适当的 IAuthenticatedEncryptorDescriptorDeserializer 实例,将文档反序列化为 IAuthenticatedEncryptorDescriptor 实例,然后将其包装在单独的 IKey 实例中。IKey 实例的集合将返回到调用方。

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

IXmlRepositoryIXmlRepository

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

  • GetAllElementsIReadOnlyCollection<XElement>

  • StoreElement(XElement element, string friendlyName)

IXmlRepository 的实现不需要分析通过它们传递的 XML。它们应视为不透明的 XML 文档,让较高的层担心如何生成和分析文档。

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

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

使用其他后备存储(例如 Azure 表存储)时,注册自定义 IXmlRepository 是合适的。

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

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

IXmlEncryptorIXmlEncryptor

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

  • 加密 (XElement plaintextElement): EncryptedXmlInfo

如果序列化的 IAuthenticatedEncryptorDescriptor 包含任何标记为 "需要加密" 的元素,则 XmlKeyManager 将通过配置的 IXmlEncryptorEncrypt 方法来运行这些元素,并将到加密元素而不是纯文本元素保存到 IXmlRepositoryEncrypt 方法的输出是 EncryptedXmlInfo 对象。此对象是包含所生成的到加密 XElement 的包装,该类型表示可用于破译相应元素的 IXmlDecryptor

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

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

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

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

IXmlDecryptorIXmlDecryptor

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

  • 解密 (XElement encryptedElement): XElement

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

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

  • .ctor(IServiceProvider)
  • .ctor()

备注

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

IKeyEscrowSinkIKeyEscrowSink

IKeyEscrowSink 接口表示可以执行敏感信息的委托的类型。请记住,序列化描述符可能包含敏感信息(如加密材料),这是第一个位置引入IXmlEncryptor类型的结果。但是,意外的发生,并且密钥环可以删除或损坏。

托管接口提供了紧急转义影线,允许在任何已配置的IXmlEncryptor转换之前访问原始序列化的 XML。该接口公开单个 API:

  • 应用商店 (Guid keyId、 XElement 元素)

这取决于 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 或更高版本。

  1. using System;
  2. using System.IO;
  3. using System.Xml.Linq;
  4. using Microsoft.AspNetCore.DataProtection;
  5. using Microsoft.AspNetCore.DataProtection.KeyManagement;
  6. using Microsoft.AspNetCore.DataProtection.XmlEncryption;
  7. using Microsoft.Extensions.DependencyInjection;
  8. using Microsoft.Extensions.Logging;
  9. public class Program
  10. {
  11. public static void Main(string[] args)
  12. {
  13. var serviceCollection = new ServiceCollection();
  14. serviceCollection.AddDataProtection()
  15. .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
  16. .ProtectKeysWithDpapi()
  17. .AddKeyEscrowSink(sp => new MyKeyEscrowSink(sp));
  18. var services = serviceCollection.BuildServiceProvider();
  19. // get a reference to the key manager and force a new key to be generated
  20. Console.WriteLine("Generating new key...");
  21. var keyManager = services.GetService<IKeyManager>();
  22. keyManager.CreateNewKey(
  23. activationDate: DateTimeOffset.Now,
  24. expirationDate: DateTimeOffset.Now.AddDays(7));
  25. }
  26. // A key escrow sink where keys are escrowed such that they
  27. // can be read by members of the CONTOSO\Domain Admins group.
  28. private class MyKeyEscrowSink : IKeyEscrowSink
  29. {
  30. private readonly IXmlEncryptor _escrowEncryptor;
  31. public MyKeyEscrowSink(IServiceProvider services)
  32. {
  33. // Assuming I'm on a machine that's a member of the CONTOSO
  34. // domain, I can use the Domain Admins SID to generate an
  35. // encrypted payload that only they can read. Sample SID from
  36. // https://technet.microsoft.com/library/cc778824(v=ws.10).aspx.
  37. _escrowEncryptor = new DpapiNGXmlEncryptor(
  38. "SID=S-1-5-21-1004336348-1177238915-682003330-512",
  39. DpapiNGProtectionDescriptorFlags.None,
  40. new LoggerFactory());
  41. }
  42. public void Store(Guid keyId, XElement element)
  43. {
  44. // Encrypt the key element to the escrow encryptor.
  45. var encryptedXmlInfo = _escrowEncryptor.Encrypt(element);
  46. // A real implementation would save the escrowed key to a
  47. // write-only file share or some other stable storage, but
  48. // in this sample we'll just write it out to the console.
  49. Console.WriteLine($"Escrowing key {keyId}");
  50. Console.WriteLine(encryptedXmlInfo.EncryptedElement);
  51. // Note: We cannot read the escrowed key material ourselves.
  52. // We need to get a member of CONTOSO\Domain Admins to read
  53. // it for us in the event we need to recover it.
  54. }
  55. }
  56. }
  57. /*
  58. * SAMPLE OUTPUT
  59. *
  60. * Generating new key...
  61. * Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
  62. * <encryptedKey>
  63. * <!-- This key is encrypted with Windows DPAPI-NG. -->
  64. * <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
  65. * <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
  66. * </encryptedKey>
  67. */