共用方式為


解除保護已在 ASP.NET Core 中撤銷其金鑰的承載

ASP.NET Core 資料保護 API 主要不適合無限期保存機密承載。 Windows CNG DPAPIAzure Rights Management 等其他技術更適合無限期儲存體的案例,而且具有對應的強金鑰管理功能。 也就是說,沒有什麼可以禁止開發人員使用 ASP.NET Core 資料保護 API 來長期保護機密資料。 金鑰永遠不會從金鑰環中移除,因此只要金鑰可用且有效,IDataProtector.Unprotect 一律可以復原現有的承載。

不過,當開發人員嘗試解除保護已撤銷金鑰保護的資料時,就會發生問題,因為在此情況下,IDataProtector.Unprotect 會擲回例外狀況。 這可能適用於短期或暫時性承載 (例如驗證權杖),因為系統可以輕鬆地重新建立這類承載,而且在最壞情況下,網站訪客可能需要再次登入。 但對於持續性承載而言,Unprotect 擲回可能會導致無法接受的資料遺失。

IPersistedDataProtector

為了支援即使面對撤銷的金鑰,也允許未保護承載的案例,資料保護系統包含 IPersistedDataProtector 類型。 若要取得 IPersistedDataProtector 執行個體,只需以一般方式取得 IDataProtector 執行個體,並嘗試將 IDataProtector 轉換成 IPersistedDataProtector

注意

並非所有 IDataProtector 執行個體都可以轉換成 IPersistedDataProtector。 開發人員應使用 C# 作為運算子或類似專案,以避免因無效轉換所造成的執行時間例外狀況,而且應該準備好適當地處理失敗案例。

IPersistedDataProtector 公開下列 API 介面:

DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors,
     out bool requiresMigration, out bool wasRevoked) : byte[]

此 API 會接受受保護的承載 (作為位元組陣列),並傳回未受保護的承載。 沒有以字串為基礎映像的多載。 兩個 out 參數如下所示。

  • requiresMigration:如果用來保護此承載的金鑰不再是使用中預設金鑰,例如,用來保護此承載的金鑰已過時,且自那以後已執行金鑰輪流作業,則會設定為 true。 呼叫端可能會想要考慮根據其業務需求重新保護承載。

  • wasRevoked:如果用來保護此承載的金鑰已撤銷,則會設定為 true。

警告

ignoreRevocationErrors: true 傳遞至 DangerousUnprotect 方法時,請特別小心。 如果在呼叫此方法之後,wasRevoked 值為 true,則會撤銷用來保護此承載的索引鍵,且承載的真實性應視為可疑。 在此情況下,只有當您有一些個別的保證,即它是正宗的,例如,它來自安全資料庫,而不是由不受信任的 Web 用戶端傳送時,才繼續操作未受保護的承載。

using System;
using System.IO;
using System.Text;
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();

        // get a protector and perform a protect operation
        var protector = services.GetDataProtector("Sample.DangerousUnprotect");
        Console.Write("Input: ");
        byte[] input = Encoding.UTF8.GetBytes(Console.ReadLine());
        var protectedData = protector.Protect(input);
        Console.WriteLine($"Protected payload: {Convert.ToBase64String(protectedData)}");

        // demonstrate that the payload round-trips properly
        var roundTripped = protector.Unprotect(protectedData);
        Console.WriteLine($"Round-tripped payload: {Encoding.UTF8.GetString(roundTripped)}");

        // get a reference to the key manager and revoke all keys in the key ring
        var keyManager = services.GetService<IKeyManager>();
        Console.WriteLine("Revoking all keys in the key ring...");
        keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");

        // try calling Protect - this should throw
        Console.WriteLine("Calling Unprotect...");
        try
        {
            var unprotectedPayload = protector.Unprotect(protectedData);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }

        // try calling DangerousUnprotect
        Console.WriteLine("Calling DangerousUnprotect...");
        try
        {
            IPersistedDataProtector persistedProtector = protector as IPersistedDataProtector;
            if (persistedProtector == null)
            {
                throw new Exception("Can't call DangerousUnprotect.");
            }

            bool requiresMigration, wasRevoked;
            var unprotectedPayload = persistedProtector.DangerousUnprotect(
                protectedData: protectedData,
                ignoreRevocationErrors: true,
                requiresMigration: out requiresMigration,
                wasRevoked: out wasRevoked);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
            Console.WriteLine($"Requires migration = {requiresMigration}, was revoked = {wasRevoked}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Input: Hello!
 * Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
 * Round-tripped payload: Hello!
 * Revoking all keys in the key ring...
 * Calling Unprotect...
 * CryptographicException: The key {...} has been revoked.
 * Calling DangerousUnprotect...
 * Unprotected payload: Hello!
 * Requires migration = True, was revoked = True
 */