Share via


ASP.NET Core 中的金鑰管理擴充性

閱讀本節之前請先閱讀金鑰管理一節,因為它說明這些 API 背後的一些基本概念。

警告:實作以下任何介面的型別對於多個呼叫端來說應該是安全執行緒。

按鍵

IKey 介面是密碼編譯系統中的金鑰基本表示法。 這裡使用的術語「金鑰」是抽象意義上的,而不是字面上的「密碼編譯金鑰資料」。 金鑰具有下列屬性:

  • 啟用、建立和到期日

  • 撤銷狀態

  • 金鑰識別碼 (GUID)

此外,IKey 會公開 CreateEncryptor 方法,可用來建立繫結至此金鑰的 IAuthenticatedEncryptor 執行個體。

此外,IKey 會公開 CreateEncryptorInstance 方法,可用來建立繫結至此金鑰的 IAuthenticatedEncryptor 執行個體。

注意

沒有 API 可從 IKey 執行個體擷取原始密碼編譯資料。

IKeyManager

IKeyManager 介面代表負責一般金鑰儲存、擷取和操作的物件。 它會公開三個高階作業:

  • 建立新的金鑰,並將它保存至儲存體。

  • 從儲存體取得所有金鑰。

  • 撤銷一或多個金鑰,並將撤銷資訊保存至儲存體。

警告

撰寫 IKeyManager 是一項非常進階的工作,大多數開發人員都不應該嘗試。 相反地,大多數開發人員都應該利用 XmlKeyManager 類別所提供的功能。

XmlKeyManager

XmlKeyManager 型別是 IKeyManager 的內建具體實作。 它提供數個有用的功能,包括金鑰委付和待用金鑰的加密。 此系統中的金鑰會以 XML 元素表示 (特別是 XElement)。

XmlKeyManager 在完成其工作的過程中,相依於其他幾個元件:

  • AlgorithmConfiguration,指定新金鑰所使用的演算法。

  • IXmlRepository,控制金鑰保存在儲存體中的位置。

  • IXmlEncryptor [選擇性],允許待用加密金鑰。

  • IKeyEscrowSink [選擇性],提供金鑰委付服務。

  • IXmlRepository,控制金鑰保存在儲存體中的位置。

  • IXmlEncryptor [選擇性],允許待用加密金鑰。

  • IKeyEscrowSink [選擇性],提供金鑰委付服務。

以下是高階圖表,指出這些元件如何在 XmlKeyManager 內連接在一起。

Key Creation

金鑰建立 / CreateNewKey

CreateNewKey 的實作中,AlgorithmConfiguration 元件是用來建立唯一的 IAuthenticatedEncryptorDescriptor,然後序列化為 XML。 如果金鑰委付接收器存在,則會將原始的 (未加密) XML 提供給長期儲存體的接收器。 然後,系統會透過 IXmlEncryptor 執行未加密的 XML (若需要),以產生加密的 XML 文件。 此加密文件會透過 IXmlRepository 保存至長期儲存體。 (如果未設定 IXmlEncryptor,則未加密文件會保存在 IXmlRepository 中。)

Key Retrieval

Key Creation

金鑰建立 / CreateNewKey

CreateNewKey 的實作中,IAuthenticatedEncryptorConfiguration 元件是用來建立唯一的 IAuthenticatedEncryptorDescriptor,然後序列化為 XML。 如果金鑰委付接收器存在,則會將原始的 (未加密) XML 提供給長期儲存體的接收器。 然後,系統會透過 IXmlEncryptor 執行未加密的 XML (若需要),以產生加密的 XML 文件。 此加密文件會透過 IXmlRepository 保存至長期儲存體。 (如果未設定 IXmlEncryptor,則未加密文件會保存在 IXmlRepository 中。)

Key Retrieval

金鑰擷取 / 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 方法執行這些元素,並將加密的元素而非純文字元素保存至 IXmlRepositoryEncrypt 方法的輸出是 EncryptedXmlInfo 物件。 此物件是包裝函式,其中包含產生的加密 XElement 和 Type,其代表可用來解密對應元素的 IXmlDecryptor

有四個實作 IXmlEncryptor 的內建具體型別:

如需詳細資訊,請參閱待用金鑰加密文件

若要變更整個應用程式的預設金鑰加密待用機制,請註冊自訂 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.AddSingletonIServiceCollection.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>
 */