데이터 보호: 비-DI 인식 시나리오

등록일시: 2017-04-21 08:00,  수정일시: 2017-04-21 08:00
조회수: 3,429
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 DI를 사용할 수 없는 상황에서 데이터 보호 시스템을 사용하는 방법에 관해서 살펴봅니다.

데이터 보호 시스템은 일반적으로 서비스 컨테이너에 추가되어 DI 메커니즘을 통해서 종속 구성 요소에 제공되도록 설계되었습니다. 그러나 경우에 따라서는 이런 구성이 불가능한 경우도 있으며, 그 대표적인 사례가 기존 응용 프로그램에 데이터 보호 시스템을 추가하는 경우입니다.

Microsoft.AspNetCore.DataProtection.Extensions 패키지는 이런 시나리오를 지원하기 위해서 DI 관련 코드 경로를 거치지 않고도 데이터 보호 시스템을 사용할 수 있도록 간편한 방법을 제공해주는 DataProtectionProvider 구체 형식을 제공합니다. 이 형식은 자체적으로 IDataProtectionProvider 인터페이스를 구현하고 있으며 생성하는 방법도 매우 쉬워서, 해당 공급자의 암호화 키를 저장할 위치를 가리키는 DirectoryInfo 개체를 전달하기만 하면 됩니다.

사용 예제:

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // get the path to %LOCALAPPDATA%\myapp-keys
        string destFolder = Path.Combine(
            Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder));

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        string input = Console.ReadLine();

        // protect the payload
        string protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // unprotect the payload
        string unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Enter input: Hello world!
 * Protect returned: CfDJ8FWbAn6...ch3hAPm1NJA
 * Unprotect returned: Hello world!
 */
주의

DataProtectionProvider 구체 형식은 기본적으로 원시 키 관련 자료를 파일 시스템에 저장하기 전에 암호화 하지 않습니다. 이는 개발자가 네트워크 공유를 지정하는 경우를 지원하기 위한 것으로, 그런 경우에는 데이터 보호 시스템이 적절한 저장된 비활성 키 암호화 메커니즘을 자동으로 추론할 수가 없습니다.

또한, 기본적으로 DataProtectionProvider 구체 형식은 응용 프로그램을 격리하지 않기 때문에, 동일한 키 디렉터리를 가리키는 모든 응용 프로그램들은 용도 매개변수가 일치하기만 하면 페이로드를 공유할 수 있습니다.

필요하다면 응용 프로그램 개발자가 이 문제점들을 모두 해결할 수 있습니다. DataProtectionProvider 구체 형식의 생성자에는 데이터 보호 시스템의 동작을 제어할 때 사용되는 선택적 구성 콜백을 전달할 수 있습니다. 다음 예제는 명시적으로 SetApplicationName 메서드를 호출해서 격리 기능을 활성화하고, 자동으로 Windows DPAPI를 이용해서 저장되는 키를 암호화하도록 시스템을 구성하는 방법을 보여줍니다. 만약 디렉터리가 UNC 공유를 가리키고 있다면, 관련된 모든 머신에 공유 인증서를 배포한 다음, ProtectKeysWithCertificate 메서드를 호출해서 시스템이 대신 인증서 기반 암호화를 사용하도록 구성할 수도 있습니다.

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // get the path to %LOCALAPPDATA%\myapp-keys
        string destFolder = Path.Combine(
            Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder),
            configuration =>
            {
                configuration.SetApplicationName("my app name");
                configuration.ProtectKeysWithDpapi();
            });

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        string input = Console.ReadLine();

        // protect the payload
        string protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // unprotect the payload
        string unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
    }
}

DataProtectionProvider 구체 형식 생성은 비용이 많이 드는 작업입니다. 만약, 응용 프로그램이 이 형식의 인스턴스를 여러 개 관리하지만 모든 인스턴스가 동일한 키 저장소 디렉터리를 가리킨다면 응용 프로그램의 성능이 저하될 수 있습니다. 권장되는 사용 방식은 응용 프로그램 개발자가 이 형식의 인스턴스를 한 번만 생성한 다음, 해당 단일 참조를 가능한 여러 번 재사용하는 것입니다. DataProtectionProvider 형식과 이 형식으로부터 생성된 모든 IDataProtector의 인스턴스는 다중 호출자에 대해 스레드로부터 안전(Thread-Safe)합니다.