ASP.NET Core의 키 관리

데이터 보호 시스템은 페이로드를 보호 및 보호 해제하는 데 사용되는 마스터 키의 수명을 자동으로 관리합니다. 각 키는 다음 네 단계 중 하나에 있을 수 있습니다.

  • 생성됨 - 키가 키 링에 있지만 아직 활성화되지 않았습니다. 키가 이 키 링을 사용하는 모든 머신에 전파될 수 있는 충분한 시간이 경과할 때까지 새 보호 작업에 키를 사용하면 안 됩니다.

  • 활성 - 키 링에 키가 있으며 모든 새 보호 작업에 사용해야 합니다.

  • 만료됨 - 키가 기본 수명을 실행했으므로 새 보호 작업에 더 이상 사용되지 않습니다.

  • 해지됨 - 키가 손상되었으므로 새 보호 작업에 사용해서는 안 됩니다.

생성, 활성 및 만료된 키는 모두 들어오는 페이로드의 보호를 해제하는 데 사용할 수 있습니다. 기본적으로 해지된 키는 페이로드 보호를 해제하는 데 사용할 수 없지만, 필요한 경우 애플리케이션 개발자가 이 동작을 재정의할 수 있습니다.

경고

개발자는 키 링에서 키를 삭제하려고 할 수 있습니다(예: 파일 시스템에서 해당 파일 삭제). 이 시점에서 키로 보호되는 모든 데이터는 영구적으로 해독할 수 없으며 해지된 키와 같은 비상 재정의가 없습니다. 키를 삭제하는 것은 진정한 파괴적인 동작이므로 데이터 보호 시스템은 이 작업을 수행하기 위한 최고 수준의 API를 노출하지 않습니다.

기본 키 선택

데이터 보호 시스템은 백업 리포지토리에서 키 링을 읽을 때 키 링에서 "기본" 키를 찾으려고 시도합니다. 기본 키는 새 보호 작업에 사용됩니다.

일반적인 추론은 데이터 보호 시스템이 가장 최근에 활성화된 날짜의 키를 기본 키로 선택한다는 것입니다. (서버 간 클럭 오차를 허용하는 약간의 fudge 요인이 있습니다.) 키가 만료되거나 해지되고 애플리케이션에서 자동 키 생성을 비활성화하는 경우 아래의 키 만료 및 롤링 정책에 따라 즉시 활성화하여 새 키가 생성됩니다.

데이터 보호 시스템에서 다른 키로 폴백하는 대신 새 키를 즉시 생성하는 이유는 새 키 생성이 새 키 이전에 활성화된 모든 키의 암시적 만료로 처리되어야 하기 때문입니다. 일반적인 아이디어는 새 키가 이전 키와 다른 알고리즘 또는 미사용 암호화 메커니즘으로 구성된 것일 수 있으며 시스템은 폴백보다 현재 구성을 사용하는 것이 좋습니다.

예외가 있습니다. 애플리케이션 개발자가 자동 키 생성을 사용하지 않도록 설정한 경우 데이터 보호 시스템은 기본 키로 항목을 선택해야 합니다. 이 대체 시나리오에서 시스템은 가장 최근의 활성화된 날짜의 해지되지 않은 키를 선택하고, 클러스터의 다른 머신으로 전파하는 데 시간이 오래 걸리는 키에 대한 기본 설정을 지정합니다. 결과적으로 대체 시스템에서 만료된 기본 키를 선택할 수 있습니다. 대체 시스템은 해지된 키를 기본 키로 선택하지 않으며, 키 링이 비어 있거나 모든 키가 해지된 경우 초기화 시 시스템에서 오류를 생성합니다.

키 만료 및 롤링

키가 생성되면 자동으로 활성화 날짜를 {지금 + 2일}로 지정하고 만료 날짜를 {지금 + 90일}로 지정합니다. 활성화하기 전 2일 지연은 시스템을 통해 전파되는 키 시간을 제공합니다. 즉, 백업 저장소를 가리키는 다른 애플리케이션이 다음 자동 새로 고침 기간에 키를 관찰할 수 있도록 하여 키 링이 활성화될 때 키 링을 사용해야 할 수 있는 모든 애플리케이션에 전파될 가능성을 극대화할 수 있습니다.

기본 키가 2일 이내에 만료되고 기본 키가 만료될 때 활성화되는 키가 키 링에 아직 없는 경우 데이터 보호 시스템은 자동으로 새 키를 키 링에 유지합니다. 이 새 키의 활성화 날짜는 {기본 키의 만료 날짜}이고, 만료 날짜는 {지금 + 90일}입니다. 이렇게 하면 시스템에서 서비스 중단 없이 정기적으로 키를 자동으로 롤링할 수 있습니다.

키가 즉시 활성화되어 생성되는 경우가 있을 수 있습니다. 한 가지 예는 애플리케이션이 한 동안 실행되지 않고 키 링의 모든 키가 만료된 경우입니다. 이 경우 키는 2일의 정상적인 활성화 지연 없이 활성화 날짜가 {지금}으로 지정됩니다.

기본 키 수명은 90일이지만 다음 예제와 같이 구성할 수 있습니다.

services.AddDataProtection()
       // use 14-day lifetime instead of 90-day lifetime
       .SetDefaultKeyLifetime(TimeSpan.FromDays(14));

관리자는 기본 시스템 전체를 변경할 수도 있지만 SetDefaultKeyLifetime을 명시적으로 호출하면 시스템 전체 정책이 무시됩니다. 기본 키 수명은 7일보다 짧을 수 없습니다.

자동 키 링 새로 고침

데이터 보호 시스템이 초기화되면 기본 리포지토리에서 키 링을 읽고 메모리에 캐시합니다. 이 캐시를 사용하면 백업 저장소에 영향을 주지 않고 보호 및 보호 해제 작업을 진행할 수 있습니다. 시스템은 약 24시간마다 또는 현재 기본 키가 만료되는 시간 중 먼저 도래하는 시간에 백업 저장소에서 변경 내용을 자동으로 검사합니다.

경고

개발자는 키 관리 API를 직접 사용해야 하는 경우는 거의 없습니다. 데이터 보호 시스템은 위에서 설명한 대로 자동 키 관리를 수행합니다.

데이터 보호 시스템은 키 링을 검사하고 변경하는 데 사용할 수 있는 인터페이스 IKeyManager를 노출합니다. IDataProtectionProvider의 인스턴스를 제공한 DI 시스템은 사용을 위해 IKeyManager의 인스턴스를 제공할 수도 있습니다. 또는 아래 예제와 같이 IServiceProvider에서 IKeyManager를 직접 끌어올 수 있습니다.

키 링을 수정하는 모든 작업(새 키를 명시적으로 생성하거나 해지 수행)은 메모리 내 캐시를 무효화합니다. Protect 또는 Unprotect에 대한 다음 호출로 인해 데이터 보호 시스템이 키 링을 다시 읽고 캐시를 다시 만듭니다.

아래 샘플에서는 기존 키를 취소하고 수동으로 새 키를 생성하는 등의 방법으로 IKeyManager 인터페이스를 사용하여 키 링을 검사 및 조작하는 방법을 보여줍니다.

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

        // perform a protect operation to force the system to put at least
        // one key in the key ring
        services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
        Console.WriteLine("Performed a protect operation.");
        Thread.Sleep(2000);

        // get a reference to the key manager
        var keyManager = services.GetService<IKeyManager>();

        // list all keys in the key ring
        var allKeys = keyManager.GetAllKeys();
        Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
        foreach (var key in allKeys)
        {
            Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
        }

        // revoke all keys in the key ring
        keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
        Console.WriteLine("Revoked all existing keys.");

        // add a new key to the key ring with immediate activation and a 1-month expiration
        keyManager.CreateNewKey(
            activationDate: DateTimeOffset.Now,
            expirationDate: DateTimeOffset.Now.AddMonths(1));
        Console.WriteLine("Added a new key.");

        // list all keys in the key ring
        allKeys = keyManager.GetAllKeys();
        Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
        foreach (var key in allKeys)
        {
            Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Performed a protect operation.
 * The key ring contains 1 key(s).
 * Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = False
 * Revoked all existing keys.
 * Added a new key.
 * The key ring contains 2 key(s).
 * Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = True
 * Key {2266fc40-e2fb-48c6-8ce2-5fde6b1493f7}: Created = 2015-03-18 22:20:51Z, IsRevoked = False
 */

영어 이외의 언어로 번역된 코드 주석을 보려면 이 GitHub 토론 이슈에서 알려주세요.

키 스토리지

데이터 보호 시스템에는 적절한 키 스토리지 위치와 미사용 암호화 메커니즘이 자동으로 추론되도록 하는 추론 기능이 있습니다. 키 지속성 메커니즘도 앱 개발자가 구성할 수 있습니다. 다음 문서에서는 이러한 메커니즘의 기본 구현에 대해 설명합니다.