ASP.NET Core의 키 관리 확장성

이 섹션을 읽기 전에 키 관리 섹션을 읽어보세요. 이러한 API의 이면에 있는 몇 가지 기본 개념을 설명합니다.

경고: 다음 인터페이스 중 하나를 구현하는 형식은 여러 호출자에 대해 스레드로부터 안전해야 합니다.

IKey 인터페이스는 암호화 시스템에 있는 키의 기본 표현입니다. 여기서 키라는 용어는 "암호화 키 자료"의 리터럴 의미가 아니라 추상적인 의미로 사용됩니다. 키에는 다음과 같은 속성이 있습니다.

  • 활성화, 만들기 및 만료 날짜

  • 해지 상태

  • 키 식별자(GUID)

또한 IKey는 이 키에 연결된 IAuthenticatedEncryptor 인스턴스를 만드는 데 사용할 수 있는 CreateEncryptor 메서드를 노출합니다.

또한 IKey는 이 키에 연결된 IAuthenticatedEncryptor 인스턴스를 만드는 데 사용할 수 있는 CreateEncryptorInstance 메서드를 노출합니다.

참고

IKey 인스턴스에서 원시 암호화 자료를 검색하는 API가 없습니다.

IKeyManager

IKeyManager 인터페이스는 일반 키 스토리지, 검색 및 조작을 담당하는 개체를 나타냅니다. 세 가지 상위 수준 작업을 노출합니다.

  • 새 키를 만들고 스토리지에 유지합니다.

  • 스토리지에서 모든 키를 가져옵니다.

  • 하나 이상의 키를 해지하고 해지 정보를 스토리지에 유지합니다.

경고

IKeyManager 작성은 매우 고급 작업이며 대부분의 개발자는 이 작업을 시도해서는 안 됩니다. 대신 대부분의 개발자는 XmlKeyManager 클래스에서 제공하는 기능을 활용해야 합니다.

XmlKeyManager

XmlKeyManager 형식은 IKeyManager의 기본 제공되는 구체적인 구현입니다. 키 에스크로 및 미사용 키 암호화를 비롯한 몇 가지 유용한 기능을 제공합니다. 이 시스템의 키는 XML 요소(특히 XElement)로 표시됩니다.

XmlKeyManager는 작업을 수행하는 과정에서 몇 가지 다른 구성 요소에 따라 달라집니다.

  • AlgorithmConfiguration는 새 키에서 사용하는 알고리즘을 나타냅니다.

  • IXmlRepository는 스토리지에서 키가 유지되는 위치를 제어합니다.

  • IXmlEncryptor[선택 사항] - 미사용 키를 암호화할 수 있습니다.

  • IKeyEscrowSink[선택 사항] - 주요 에스크로 서비스를 제공합니다.

  • IXmlRepository는 스토리지에서 키가 유지되는 위치를 제어합니다.

  • IXmlEncryptor[선택 사항] - 미사용 키를 암호화할 수 있습니다.

  • IKeyEscrowSink[선택 사항] - 주요 에스크로 서비스를 제공합니다.

다음은 이러한 구성 요소가 XmlKeyManager 내에서 함께 연결되는 방법을 나타내는 상위 수준 다이어그램입니다.

Key Creation

키 만들기 / CreateNewKey

CreateNewKey 구현에서 AlgorithmConfiguration 구성 요소는 고유한 IAuthenticatedEncryptorDescriptor를 만드는 데 사용되며, 이 구성 요소는 XML로 직렬화됩니다. 키 에스크로 싱크가 있는 경우 장기 스토리지를 위해 원시(암호화되지 않은) XML이 싱크에 제공됩니다. 그런 다음, 암호화되지 않은 XML은 IXmlEncryptor(필요한 경우)를 통해 실행되어 암호화된 XML 문서를 생성합니다. 이 암호화된 문서는 IXmlRepository를 통해 장기 스토리지에 유지됩니다. (IXmlEncryptor가 구성되지 않은 경우 암호화되지 않은 문서는 IXmlRepository에 유지됩니다.)

Key Retrieval

Key Creation

키 만들기 / CreateNewKey

CreateNewKey 구현에서 IAuthenticatedEncryptorConfiguration 구성 요소는 고유한 IAuthenticatedEncryptorDescriptor를 만드는 데 사용되며, 이 구성 요소는 XML로 직렬화됩니다. 키 에스크로 싱크가 있는 경우 장기 스토리지를 위해 원시(암호화되지 않은) XML이 싱크에 제공됩니다. 그런 다음, 암호화되지 않은 XML은 IXmlEncryptor(필요한 경우)를 통해 실행되어 암호화된 XML 문서를 생성합니다. 이 암호화된 문서는 IXmlRepository를 통해 장기 스토리지에 유지됩니다. (IXmlEncryptor가 구성되지 않은 경우 암호화되지 않은 문서는 IXmlRepository에 유지됩니다.)

Key Retrieval

키 검색 / GetAllKeys

GetAllKeys 구현에서 키 및 해지를 나타내는 XML 문서는 기본 IXmlRepository에서 읽습니다. 이러한 문서가 암호화되면 시스템에서 자동으로 암호를 해독합니다. XmlKeyManager는 문서를 IAuthenticatedEncryptorDescriptorDeserializer 인스턴스로 다시 역직렬화하기 위해 적절한 IAuthenticatedEncryptorDescriptor 인스턴스를 만든 다음, 개별 IKey 인스턴스에 래핑합니다. IKey 인스턴스의 이 컬렉션은 호출자에게 반환됩니다.

특정 XML 요소에 대한 자세한 내용은 키 스토리지 형식 문서에서 찾을 수 있습니다.

IXmlRepository

IXmlRepository 인터페이스는 XML을 백업 저장소에 보관하고 XML을 검색할 수 있는 형식을 나타냅니다. 두 개의 API를 노출합니다.

  • GetAllElements :IReadOnlyCollection<XElement>

  • StoreElement(XElement element, string friendlyName)

IXmlRepository의 구현은 전달된 XML을 구문 분석할 필요가 없습니다. XML 문서를 불투명한 것으로 처리하고 상위 계층에서 문서 생성 및 구문 분석하도록 해야 합니다.

IXmlRepository를 구현하는 네 가지 기본 제공된 구체적인 형식이 있습니다.

자세한 내용은 키 스토리지 공급자 문서를 참조하세요.

다른 백업 저장소(예: Azure Table Storage)를 사용하는 경우 사용자 지정 IXmlRepository를 등록하는 것이 적절합니다.

애플리케이션 전체에서 기본 리포지토리를 변경하려면 사용자 지정 IXmlRepository 인스턴스를 등록합니다.

services.Configure<KeyManagementOptions>(options => options.XmlRepository = new MyCustomXmlRepository());
services.AddSingleton<IXmlRepository>(new MyCustomXmlRepository());

IXmlEncryptor

IXmlEncryptor 인터페이스는 일반 텍스트 XML 요소를 암호화할 수 있는 형식을 나타냅니다. 단일 API를 노출합니다.

  • Encrypt(XElement plaintextElement) : EncryptedXmlInfo

직렬화된 IAuthenticatedEncryptorDescriptor에 "암호화 필요"로 표시된 요소가 포함된 경우 XmlKeyManager는 구성된 IXmlEncryptorEncrypt 메서드를 통해 해당 요소를 실행하고 일반 텍스트 요소가 아닌 암호화된 요소를 IXmlRepository에 유지합니다. Encrypt 메서드의 출력은 EncryptedXmlInfo 개체입니다. 이 개체는 결과적으로 암호화된 XElement 및 해당 요소를 해독하는 데 사용할 수 있는 IXmlDecryptor를 나타내는 유형을 모두 포함하는 래퍼입니다.

IXmlEncryptor를 구현하는 네 가지 기본 제공된 구체적인 형식이 있습니다.

자세한 내용은 미사용 키 암호화 문서를 참조하세요.

애플리케이션 전체에서 기본 미사용 키암호화 메커니즘을 변경하려면 사용자 지정 IXmlEncryptor 인스턴스를 등록합니다.

services.Configure<KeyManagementOptions>(options => options.XmlEncryptor = new MyCustomXmlEncryptor());
services.AddSingleton<IXmlEncryptor>(new MyCustomXmlEncryptor());

IXmlDecryptor

IXmlDecryptor 인터페이스는 IXmlEncryptor를 통해 암호화된 XElement의 암호를 해독하는 방법을 알고 있는 형식을 나타냅니다. 단일 API를 노출합니다.

  • Decrypt(XElement encryptedElement) : XElement

Decrypt 메서드는 IXmlEncryptor.Encrypt에서 수행하는 암호화를 실행 취소합니다. 일반적으로 각 구체적인 IXmlEncryptor 구현에는 해당하는 구체적인 IXmlDecryptor 구현이 있습니다.

IXmlDecryptor를 구현하는 형식에는 다음 두 개의 공용 생성자 중 하나가 있어야 합니다.

  • .ctor(IServiceProvider)
  • .ctor()

참고

생성자에 전달된 IServiceProvider는 Null일 수 있습니다.

IKeyEscrowSink

IKeyEscrowSink 인터페이스는 중요한 정보의 에스크로를 수행할 수 있는 형식을 나타냅니다. 직렬화된 설명자에는 중요한 정보(예: 암호화 자료)가 포함될 수 있으며, 이로 인해 IXmlEncryptor 형식이 처음으로 도입되었습니다. 그러나 인시던트가 발생하고 키 링이 삭제되거나 손상될 수 있습니다.

에스크로 인터페이스는 구성된 IXmlEncryptor에 의해 변환되기 전에 직렬화된 원시 XML에 액세스할 수 있도록 응급 이스케이프 해지를 제공합니다. 인터페이스는 단일 API를 노출합니다.

  • 스토어(Guid keyId, XElement 요소)

비즈니스 정책과 일치하는 안전한 방식으로 제공된 요소를 처리하는 것은 IKeyEscrowSink 구현에 달려 있습니다. 한 가지 가능한 구현은 에스크로 싱크가 인증서의 프라이빗 키가 에스크로된 알려진 회사 X.509 인증서를 사용하여 XML 요소를 암호화하는 것입니다. CertificateXmlEncryptor 형식이 이 작업을 지원할 수 있습니다. 또한 IKeyEscrowSink 구현은 제공된 요소를 적절하게 유지합니다.

기본적으로 에스크로 메커니즘은 활성화되어 있지 않지만 서버 관리자는 이를 전역적으로 구성할 수 있습니다. 아래 샘플과 같이 IDataProtectionBuilder.AddKeyEscrowSink 메서드를 통해 프로그래밍 방식으로 구성할 수도 있습니다. IKeyEscrowSink 인스턴스가 싱글톤으로 사용되기 때문에 AddKeyEscrowSink 메서드 오버로드는 IServiceCollection.AddSingletonIServiceCollection.AddInstance 오버로드를 미러링합니다. 여러 IKeyEscrowSink 인스턴스가 등록되면 키 생성 중에 각 인스턴스가 호출되므로 키를 동시에 여러 메커니즘에 에스크로할 수 있습니다.

IKeyEscrowSink 인스턴스에서 자료를 읽을 API가 없습니다. 이는 에스크로 메커니즘의 디자인 이론과 일치합니다. 즉, 신뢰할 수 있는 기관에서 키 자료에 액세스할 수 있도록 하기 위한 것이며, 애플리케이션 자체가 신뢰할 수 있는 기관이 아니므로 자체 에스크로된 자료에 액세스할 수 없어야 합니다.

다음 샘플 코드에서는 "CONTOSODomain Admins"의 멤버만 복구할 수 있도록 키가 에스크로되는 IKeyEscrowSink를 만들고 등록하는 방법을 보여줍니다.

참고

이 샘플을 실행하려면 도메인에 가입된 Windows 8/Windows Server 2012 머신에 있어야 하며 도메인 컨트롤러는 Windows Server 2012 이상이어야 합니다.

using System;
using System.IO;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
            .ProtectKeysWithDpapi()
            .AddKeyEscrowSink(sp => new MyKeyEscrowSink(sp));
        var services = serviceCollection.BuildServiceProvider();

        // get a reference to the key manager and force a new key to be generated
        Console.WriteLine("Generating new key...");
        var keyManager = services.GetService<IKeyManager>();
        keyManager.CreateNewKey(
            activationDate: DateTimeOffset.Now,
            expirationDate: DateTimeOffset.Now.AddDays(7));
    }

    // A key escrow sink where keys are escrowed such that they
    // can be read by members of the CONTOSO\Domain Admins group.
    private class MyKeyEscrowSink : IKeyEscrowSink
    {
        private readonly IXmlEncryptor _escrowEncryptor;

        public MyKeyEscrowSink(IServiceProvider services)
        {
            // Assuming I'm on a machine that's a member of the CONTOSO
            // domain, I can use the Domain Admins SID to generate an
            // encrypted payload that only they can read. Sample SID from
            // https://technet.microsoft.com/library/cc778824(v=ws.10).aspx.
            _escrowEncryptor = new DpapiNGXmlEncryptor(
                "SID=S-1-5-21-1004336348-1177238915-682003330-512",
                DpapiNGProtectionDescriptorFlags.None,
                new LoggerFactory());
        }

        public void Store(Guid keyId, XElement element)
        {
            // Encrypt the key element to the escrow encryptor.
            var encryptedXmlInfo = _escrowEncryptor.Encrypt(element);

            // A real implementation would save the escrowed key to a
            // write-only file share or some other stable storage, but
            // in this sample we'll just write it out to the console.
            Console.WriteLine($"Escrowing key {keyId}");
            Console.WriteLine(encryptedXmlInfo.EncryptedElement);

            // Note: We cannot read the escrowed key material ourselves.
            // We need to get a member of CONTOSO\Domain Admins to read
            // it for us in the event we need to recover it.
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Generating new key...
 * Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
 * <encryptedKey>
 *   <!-- This key is encrypted with Windows DPAPI-NG. -->
 *   <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
 *   <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
 * </encryptedKey>
 */