取消保护已吊销在 ASP.NET Core中键的有效负载Unprotect payloads whose keys have been revoked in ASP.NET Core
ASP.NET Core 数据保护 Api 主要不用于机密负载的无限期持久性。其他技术(如和Azure Rights Management )更适用于无限存储的情况,并且具有相应的强大密钥管理功能。也就是说,无需进行任何开发人员禁止使用 ASP.NET Core 数据保护 Api 进行长期保护的机密数据。密钥永远不会从密钥环中删除,因此,只要密钥可用且有效, 就可以始终恢复现有有效负载。
但是,当开发人员尝试取消保护已被吊销密钥保护的数据时,会出现问题,因为在这种情况下 IDataProtector.Unprotect
会引发异常。这对于短期或暂时性负载(例如身份验证令牌)可能很合适,因为系统可以轻松地重新创建这种类型的负载,并且在最糟糕的情况下,站点访问者可能需要再次登录。但对于持久化有效负载,具有 Unprotect
引发可能导致数据丢失不可接受。
为支持允许负载不受保护的方案(即使在面对吊销密钥的情况下),数据保护系统包含一个 IPersistedDataProtector
类型。若要获取 IPersistedDataProtector
的实例,只需以正常方式获取 IDataProtector
的实例,然后尝试将 IDataProtector
强制转换为 IPersistedDataProtector
。
并非所有 IDataProtector
实例都可以转换为 IPersistedDataProtector
。开发人员应使用C# as 运算符或类似的方法来避免由无效强制转换导致的运行时异常,并应准备适当地处理故障情况。
IPersistedDataProtector
公开以下 API 图面:
此 API 获取受保护的负载(作为字节数组)并返回未受保护的负载。没有基于字符串的重载。这两个 out 参数如下所示。
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)}");
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
{
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!
*/