ASP.NET Core 中的金鑰管理擴充性
閱讀本節之前請先閱讀金鑰管理一節,因為它說明這些 API 背後的一些基本概念。
警告:實作以下任何介面的型別對於多個呼叫端來說應該是安全執行緒。
按鍵
IKey
介面是密碼編譯系統中的金鑰基本表示法。 這裡使用的術語「金鑰」是抽象意義上的,而不是字面上的「密碼編譯金鑰資料」。 金鑰具有下列屬性:
啟用、建立和到期日
撤銷狀態
金鑰識別碼 (GUID)
此外,IKey
會公開 CreateEncryptor
方法,可用來建立繫結至此金鑰的 IAuthenticatedEncryptor 執行個體。
此外,IKey
會公開 CreateEncryptorInstance
方法,可用來建立繫結至此金鑰的 IAuthenticatedEncryptor 執行個體。
注意
沒有 API 可從 IKey
執行個體擷取原始密碼編譯資料。
IKeyManager
IKeyManager
介面代表負責一般金鑰儲存、擷取和操作的物件。 它會公開三個高階作業:
建立新的金鑰,並將它保存至儲存體。
從儲存體取得所有金鑰。
撤銷一或多個金鑰,並將撤銷資訊保存至儲存體。
警告
撰寫 IKeyManager
是一項非常進階的工作,大多數開發人員都不應該嘗試。 相反地,大多數開發人員都應該利用 XmlKeyManager 類別所提供的功能。
XmlKeyManager
XmlKeyManager
型別是 IKeyManager
的內建具體實作。 它提供數個有用的功能,包括金鑰第三方託管和 rest 金鑰的加密。 此系統中的金鑰會以 XML 元素表示 (特別是 XElement)。
XmlKeyManager
在完成其工作的過程中,相依於其他幾個元件:
AlgorithmConfiguration
,指定新金鑰所使用的演算法。IXmlRepository
,控制金鑰保存在儲存體中的位置。IXmlEncryptor
[選擇性],允許 rest 加密金鑰。IKeyEscrowSink
[選擇性],提供金鑰委付服務。
IXmlRepository
,控制金鑰保存在儲存體中的位置。IXmlEncryptor
[選擇性],允許 rest 加密金鑰。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 元素的詳細資訊,請參閱金鑰儲存體格式文件。
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
和 Type,其代表可用來解密對應元素的 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 型別的原因。 然而,意外發生時,金鑰環可能會被刪除或損壞。
委付介面提供緊急逸出介面,允許在任何已設定 IXmlEncryptor 的轉換原始序列化 XML 之前,先存取原始序列化 XML。 介面會公開單一 API:
- Store(Guid keyId, XElement element)
這取決於 IKeyEscrowSink
實作的方式,以與商務原則一致的安全方式處理所提供的元素。 其中一個可能的實作是讓委付接收器使用已知的公司 X.509 憑證來加密 XML 元素,其中憑證的私密金鑰已被委付;CertificateXmlEncryptor
型別可協助進行這項處理。 IKeyEscrowSink
實作也會負責適當地保存提供的元素。
根據預設,不會啟用委付機制,不過伺服器管理員可以全域設定此機制。 您也可以透過 IDataProtectionBuilder.AddKeyEscrowSink
方法以程式設計方式進行設定,如下列範例所示。 AddKeyEscrowSink
方法多載會鏡像 IServiceCollection.AddSingleton
和 IServiceCollection.AddInstance
多載,因為 IKeyEscrowSink
執行個體是單一資料庫。 如果註冊多個 IKeyEscrowSink
執行個體,系統會在金鑰產生期間呼叫每個執行個體,因此可以同時將金鑰委付至多個機制。
沒有 API 可從 IKeyEscrowSink
執行個體讀取資料。 這與委付機制的設計理論一致:它的目的是讓信任的授權單位可存取金鑰資料,而且由於應用程式本身不是受信任的授權單位,因此它不應該能夠存取自己的委付資料。
下列範例程式碼示範如何建立和註冊 IKeyEscrowSink
,其中金鑰被委付,以便只有「CONTOSODomain 系統管理員」的成員才能恢復它們。
注意
若要執行此範例,您必須使用已加入網域的 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>
*/