ASP.NET Core에서 해지된 키가 속한 페이로드 보호 해제
ASP.NET Core 데이터 보호 API는 주로 기밀 페이로드의 무한 지속성을 위한 것이 아닙니다. Windows CNG DPAPI 및 Azure Rights Management와 같은 다른 기술은 무한 스토리지의 시나리오에 더 적합하며 강력한 키 관리 기능을 제공합니다. 즉, 기밀 데이터의 장기 보호를 위해 개발자가 ASP.NET Core 데이터 보호 API를 사용할 필요가 없습니다. 키가 키 링에서 제거되지 않으므로 키가 사용 가능하고 유효한 경우 IDataProtector.Unprotect
는 항상 기존 페이로드를 복구할 수 있습니다.
그러나 개발자가 해지된 키로 보호되는 데이터를 보호 해제하려고 할 때 문제가 발생합니다. 이 경우 IDataProtector.Unprotect
에서 예외를 throw합니다. 이러한 종류의 페이로드를 시스템에서 쉽게 다시 만들 수 있으며, 최악의 경우 사이트 방문자가 다시 로그인해야 할 수 있으므로 수명이 짧거나 임시 페이로드(예: 인증 토큰)에는 문제가 없을 수 있습니다. 그러나 지속형 페이로드의 경우 Unprotect
throw로 인해 데이터 손실이 허용되지 않을 수 있습니다.
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
보호하는 데 사용되는 키가 해지된 경우로 설정됩니다.
Warning
ignoreRevocationErrors: true
를 DangerousUnprotect
메서드에 전달할 때는 매우 주의해야 합니다. 이 메서드를 호출한 후에는 wasRevoked
값이 true 이면 이 페이로드를 보호하는 데 사용된 키가 해지되고 페이로드의 신뢰성은 주의 대상으로 처리되어야 합니다. 이 경우 신뢰할 수 없는 웹 클라이언트에서 전송되지 않고 보안 데이터베이스에서 들어오는 것과 같이 인증된다는 별도의 보증이 있는 경우에만 보호되지 않는 페이로드에서 계속 작동합니다.
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
*/
ASP.NET Core