解除保護已在 ASP.NET Core 中撤銷其金鑰的承載
ASP.NET Core 資料保護 API 主要不適合無限期保存機密承載。 Windows CNG DPAPI 和 Azure 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
*/