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:
GetAllElements
:IReadOnlyCollection<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
将通过已配置的 IXmlEncryptor
的 Encrypt
方法运行这些元素,并将加密元素而不是纯文本元素持久保存到 IXmlRepository
。 Encrypt
方法的输出是一个 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.AddSingleton
和 IServiceCollection.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>
*/