ASP.NET Core에서 키 해지 된 페이로드 보호 해제Unprotect payloads whose keys have been revoked in ASP.NET Core

ASP.NET Core 데이터 보호 Api는 하지 주로 기밀 페이로드 무기한 유지 됩니다.The ASP.NET Core data protection APIs are not primarily intended for indefinite persistence of confidential payloads. 와 같은 다른 기술이 Windows CNG DPAPI 하 고 Azure Rights Management 무기한 저장 하는 시나리오에 보다 적합 한 마찬가지로 강력한 키 관리 기능을 갖습니다.Other technologies like Windows CNG DPAPI and Azure Rights Management are more suited to the scenario of indefinite storage, and they have correspondingly strong key management capabilities. 즉, ASP.NET Core 데이터 보호 Api를 사용 하 여 기밀 데이터의 장기 보호에 대 한 개발자를 금지 하는 항목이 없는 합니다.That said, there's nothing prohibiting a developer from using the ASP.NET Core data protection APIs for long-term protection of confidential data. 키가 키 링에서 하므로 제거 하지 IDataProtector.Unprotect 으로 키가 사용 가능 하며 올바른지에 항상 기존 페이로드를 복구할 수 있습니다.Keys are never removed from the key ring, so IDataProtector.Unprotect can always recover existing payloads as long as the keys are available and valid.

그러나 개발자가 폐기된 키를 이용해서 보호된 데이터를 보호 해제하려고 시도할 경우 문제가 발생하는데, IDataProtector.Unprotect가 예외를 던지기 때문입니다.However, an issue arises when the developer tries to unprotect data that has been protected with a revoked key, as IDataProtector.Unprotect will throw an exception in this case. 단기 및 임시 페이로드는 (인증 토큰 같은) 시스템에서 손쉽게 재생성 할 수 있으므로 이런 유형의 페이로드는 크게 문제가 되지 않으며, 최악의 경우가 발생하더라도 사용자가 다시 로그인하면 그만입니다.This might be fine for short-lived or transient payloads (like authentication tokens), as these kinds of payloads can easily be recreated by the system, and at worst the site visitor might be required to log in again. 그러나 영속화된 페이로드의 경우에는 Unprotect 메서드가 예외를 던지는 상황이 발생하면 간과할 수 없는 데이터 유실이 발생하게 됩니다.But for persisted payloads, having Unprotect throw could lead to unacceptable data loss.

IPersistedDataProtectorIPersistedDataProtector

키가 폐기된 경우에도 페이로드의 보호를 해제할 수 있도록 허용해야 하는 시나리오를 지원하기 위해서 데이터 보호 시스템에는 IPersistedDataProtector 형식이 포함되어 있습니다.To support the scenario of allowing payloads to be unprotected even in the face of revoked keys, the data protection system contains an IPersistedDataProtector type. 인스턴스를 가져옵니다 IPersistedDataProtector를 단순히 인스턴스를 가져올 IDataProtector 시도 캐스팅 고 일반적인 방식으로 IDataProtectorIPersistedDataProtector합니다.To get an instance of IPersistedDataProtector, simply get an instance of IDataProtector in the normal fashion and try casting the IDataProtector to IPersistedDataProtector.

참고

모든 IDataProtector 인스턴스를 IPersistedDataProtector형식으로 형변환 할 수 있는 것은 아닙니다.Not all IDataProtector instances can be cast to IPersistedDataProtector. 따라서 개발자는 C#의 as 연산자나 비슷한 다른 기능을 이용해서 유효하지 않은 형변환 시도로부터 발생하는 런타임 예외를 방지하고, 형변환에 실패하는 경우에 대한 적절한 처리를 준비해야 합니다.Developers should use the C# as operator or similar to avoid runtime exceptions caused by invalid casts, and they should be prepared to handle the failure case appropriately.

IPersistedDataProtector 는 다음과 같은 API 표면을 노출합니다.IPersistedDataProtector exposes the following API surface:

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

이 API는 보호된 페이로드를 전달 받아서 (byte 배열로) 보호 해제된 페이로드를 반환합니다.This API takes the protected payload (as a byte array) and returns the unprotected payload. 이 API에는 문자열 기반의 오버로드가 존재하지 않습니다.There's no string-based overload. 마지막 두 가지 out 매개변수는 다음과 같습니다.The two out parameters are as follows.

  • requiresMigration:페이로드를 보호하는데 사용된 키가 더 이상 활성화 된 기본 키가 아닌 경우 true로 설정됩니다 (페이로드를 보호하는데 사용된 키가 오래되어 키 롤링 작업이 발생한 경우 등).requiresMigration: will be set to true if the key used to protect this payload is no longer the active default key, e.g., the key used to protect this payload is old and a key rolling operation has since taken place. 업무 규칙에 따라서는 호출자가 페이로드를 다시 보호해야 하는 경우도 있습니다.The caller may wish to consider reprotecting the payload depending on their business needs.

  • wasRevoked:페이로드를 보호하는 데 사용된 키가 폐기된 경우 true로 설정됩니다.wasRevoked: will be set to true if the key used to protect this payload was revoked.

경고

DangerousUnprotect 메서드를 호출할 때 ignoreRevocationErrors: true 매개 변수를 true로 전달하는 경우에는 특히 주의하시기 바랍니다.Exercise extreme caution when passing ignoreRevocationErrors: true to the DangerousUnprotect method. 만약 이 메서드를 호출한 후 wasRevoked 값으로 true가 반환된다면, 페이로드 보호에 사용된 키가 폐기되었으며 페이로드의 신뢰성이 의심스러운 것으로 간주되어야 합니다.If after calling this method the wasRevoked value is true, then the key used to protect this payload was revoked, and the payload's authenticity should be treated as suspect. 그런 경우, 신뢰할 수 없는 웹 클라이언트에서 전송된 페이로드 등을 제외한, 보안 데이터베이스에서 가져온 페이로드처럼 별도로 신뢰성을 확실하게 보장할 수 있는 경우에만 보호 해제된 페이로드를 이용해서 작업을 수행해야 합니다.In this case, only continue operating on the unprotected payload if you have some separate assurance that it's authentic, e.g. that it's coming from a secure database rather than being sent by an untrusted web client.

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
 */