ASP.NET Core 中的金鑰管理

資料保護系統會自動管理用來保護及取消保護承載的主要金鑰存留期。 每個金鑰可以存在於四個階段的其中之一:

  • 已建立 - 金鑰存在於金鑰通道中,但尚未啟用。 此金鑰不應該用於新的保護作業,直到足夠的時間過後,該金鑰才有機會傳播到所有使用此金鑰通道的電腦。

  • 作用中 - 金鑰存在於金鑰通道中,而且應該用於所有新的 Protect 作業。

  • 已過期 - 金鑰已超過其自然存留期,不應再用於新的 Protect 作業。

  • 撤銷 - 金鑰遭到入侵,不得用於新的 Protect 作業。

已建立、作用中和已過期的金鑰都可用來將傳入承載解除保護。 根據預設,撤銷的金鑰可能無法用來解除保護承載,但應用程式開發人員可以視需要覆寫此行為

警告

開發人員可能想要從金鑰通道中刪除金鑰 (例如,從檔案系統刪除對應的檔案)。 此時,受金鑰保護的所有資料都是永久無法加密的,而且沒有緊急覆寫,就像有已撤銷的金鑰一樣。 刪除金鑰確實是破壞性的行為,因此資料保護系統不會公開任何一級 API 來執行這項作業。

預設金鑰選取

當資料保護系統從備份存放庫讀取金鑰通道時,它會嘗試從金鑰通道找出「預設」金鑰。 預設金鑰會用於新的 Protect 作業。

一般啟發學習法是資料保護系統選擇具有最新啟用日期的金鑰作為預設金鑰。 (伺服器對伺服器時鐘偏差允許存在一個小小的模糊因素,)如果金鑰已過期或撤銷,且應用程式尚未停用自動金鑰產生,則會根據下列金鑰到期和滾動原則立即產生和啟用新的金鑰。

資料保護系統立即產生新的金鑰,而不是回復到不同金鑰的原因是:新金鑰的產生應視為隱含著新金鑰之前啟用的所有金鑰均已到期。 一般概念是,新的金鑰可能已設定與舊金鑰不同的演算法或待用加密機制,而且系統應該偏好目前的設定而不是回溯。

有例外狀況。 如果應用程式開發人員已停用自動金鑰產生,則資料保護系統必須選擇某個內容做為預設金鑰。 在此後援案例中,系統會選擇具有最近啟用日期的非撤銷金鑰,並偏好有時間傳播至叢集中其他電腦的金鑰。 後援系統最終可能會選擇過期的預設金鑰。 後援系統永遠不會選擇撤銷的金鑰做為預設金鑰,如果金鑰通道是空的,或每個金鑰均已撤銷,則系統會在初始化時產生錯誤。

金鑰到期和滾動

建立金鑰時,會自動提供 {now + 2 天} 的啟用日期,以及 {now + 90 天} 的到期日。 啟用前的 2 天延遲會提供透過系統傳播的關鍵時間。 也就是說,它可以讓指向備份存放區的其他應用程式在其下一個自動重新整理期間觀察金鑰,從而最大幅度提升當金鑰通道成為作用中時,該金鑰已傳播到可能需要使用它的所有應用程式的機會。

如果預設金鑰將在 2 天內到期,且金鑰通道還沒有在預設金鑰到期時仍在使用中的金鑰,則資料保護系統會自動將新的金鑰保存至金鑰通道。 這個新金鑰的啟用日期為 {預設金鑰的到期日},到期日為 {現在 + 90 天}。 這可讓系統定期自動變換金鑰,且不會中斷服務。

在某些情況下,金鑰建立時將會立即啟用。 其中一個範例是當應用程式一段時間未執行,且金鑰通道中的所有金鑰均已到期時。 發生這種情況時,金鑰會獲得 { now } 的啟用日期,而不會有正常的 2 天啟用延遲。

預設金鑰存留期為 90 天,但此期限可以設定,如下列範例所示。

services.AddDataProtection()
       // use 14-day lifetime instead of 90-day lifetime
       .SetDefaultKeyLifetime(TimeSpan.FromDays(14));

系統管理員也可以全系統地變更預設值,不過明確呼叫 SetDefaultKeyLifetime 會覆寫任何全系統原則。 預設金鑰存留期不能少於 7 天。

自動金鑰通道重新整理

當資料保護系統初始化時,它會從基礎存放庫讀取金鑰通道,並將它快取到記憶體中。 此快取可讓保護與取消保護作業繼續,而不需要叫用支援存放區。 系統每隔 24 小時或當目前的預設金鑰到期時會自動檢查備份存放區是否有變更,端視哪一個先發生。

警告

開發人員應該很少 (如果有的話) 需要直接使用金鑰管理 API。 資料保護系統將執行自動金鑰管理,如上所述。

資料保護系統會公開介面 IKeyManager,可用來檢查和變更金鑰通道。 提供 IDataProtectionProvider 實例的 DI 系統也可以提供 IKeyManager 實例以供取用。 或者,您可以直接從 IServiceProvider 提取 IKeyManager,如下列範例所示。

任何修改金鑰通道的作業 (明確建立新的金鑰或執行撤銷) 都會使記憶體內部快取失效。 下一次呼叫 ProtectUnprotect 會導致資料保護系統重新讀取金鑰通道並重新建立快取。

下列範例示範如何使用 IKeyManager 介面來檢查及操作金鑰通道,包括撤銷現有的金鑰,並手動產生新的金鑰。

using System;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection()
            // point at a specific folder and use DPAPI to encrypt keys
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
            .ProtectKeysWithDpapi();
        var services = serviceCollection.BuildServiceProvider();

        // perform a protect operation to force the system to put at least
        // one key in the key ring
        services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
        Console.WriteLine("Performed a protect operation.");
        Thread.Sleep(2000);

        // get a reference to the key manager
        var keyManager = services.GetService<IKeyManager>();

        // list all keys in the key ring
        var allKeys = keyManager.GetAllKeys();
        Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
        foreach (var key in allKeys)
        {
            Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
        }

        // revoke all keys in the key ring
        keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
        Console.WriteLine("Revoked all existing keys.");

        // add a new key to the key ring with immediate activation and a 1-month expiration
        keyManager.CreateNewKey(
            activationDate: DateTimeOffset.Now,
            expirationDate: DateTimeOffset.Now.AddMonths(1));
        Console.WriteLine("Added a new key.");

        // list all keys in the key ring
        allKeys = keyManager.GetAllKeys();
        Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
        foreach (var key in allKeys)
        {
            Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Performed a protect operation.
 * The key ring contains 1 key(s).
 * Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = False
 * Revoked all existing keys.
 * Added a new key.
 * The key ring contains 2 key(s).
 * Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = True
 * Key {2266fc40-e2fb-48c6-8ce2-5fde6b1493f7}: Created = 2015-03-18 22:20:51Z, IsRevoked = False
 */

如果您想要查看翻譯為英文以外語言的程式碼註解,請在此 GitHub 討論問題中告訴我們。

金鑰儲存體

資料保護系統具有啟發學習法,會嘗試自動推斷適當的金鑰儲存位置和待用加密機制。 應用程式開發人員也可以設定金鑰持續性機制。 下列文件會討論這些機制的內建實作: