재우니의 블로그

ASP.NET Core 5.0 에서 IDataProtector 활용하여 데이터 보호하는 방법

 

 데이터를 암호화하고 해독하는 데 사용할 수 있는 ASP.NET Core 기본 제공 데이터 보호 메커니즘인 IDataProtector 에 대해 알아볼 것입니다.

 

 

IDataProtector로 데이터 암호화 및 암호 해독

 

IDataProtector는 데이터 보호 서비스를 제공하는 인터페이스입니다. 해당 기능을 사용하려면 해당 데이터 보호 서비스를 지정된 IServiceCollection 에 추가한 다음 종속성 주입(DI) 을 사용하여 INJECT 해야 합니다.

 

 

예를 들어 고유 키값이 guid 문자열 형태로 구분되어 있는 경우 상세화면에서 해당 URL 을 통해 쉽게 키 코드값을 보실 수 있습니다.

 

 

보호 차원 상 guid 값 파라미터를 데이터 보호 하기 위해서 아래와 같이 startup.cs 에서 서비스를 수정해 보겠습니다.

 

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<EmployeeContext>(opts =>
        opts.UseSqlServer(Configuration.GetConnectionString("sqlConnection")));
    services.AddScoped<IEmployeeRepository, EmployeeRepository>();
    
    //보호 설정
    services.AddDataProtection();
    
     services.AddControllersWithViews();
}

 

IDataProtector 및 DI 를 사용하여 데이터 보호 개체 만들기

 

이를 사용하기 위한 컨트롤러를 열어서 DI 를 통해 데이터 보호 객체를 만듭니다. IDataProtector 유형의 개체를 생성하는 것을 볼 수 있습니다. 

private readonly IDataProtector _protector;
public EmployeesController(IEmployeeRepository repo, IDataProtectionProvider provider)
{
    _repo = repo;
    _protector = provider.CreateProtector("EmployeesApp.EmployeesController");
}

 

IDataProtector 개체를 생성하였고, 이를 성공적으로 수행하기 위해서는  CreateProtector 메소드가 포함된 IDataProtectionProvider 개체를 필요로 합니다.  이 두개의 인터페이스 모두 Microsoft.AspNetCore.DataProtection 의 namespace 의 일부이므로 using 문을 통해 사용합니다.

 

이제 guid 인 id 값을 보호하기 위해서 별도로 EncryptedId 속성을 하나 만듭니다.

 

public class Employee
{
    public Guid Id { get; set; }
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }
    [Required(ErrorMessage = "Age is required")]
    public int Age { get; set; }
    [Required(ErrorMessage = "Account number is required")]
    public string AccountNumber { get; set; }
    [NotMapped]
    public string EncryptedId { get; set; }
}

 

guid 의 id 값을 foreach 구문을 통해 반복하여 id 값을 Protect() 함수를 통해 암호화 하여 새롭게 만든 속성인 EncryptedId 에 암호 값을 할당합니다.

public IActionResult Index()
{
    var employees = _repo.GetAll();
    foreach (var emp in employees)
    {
        var stringId = emp.Id.ToString();
        emp.EncryptedId = _protector.Protect(stringId);
    }
    return View(employees);
}

 

view 의 list 목록에서 상세보기 링크에 키값이 아닌 암호값을 파라미터로 전송하도록 지정합니다.

<a asp-action="Details" asp-route-id="@item.EncryptedId">Details</a> |

 

상세 화면 Detail 에서 암호화 값인 id 값을 받아 복호화 합니다. 복호화 함수는 Unprotect() 통해 변환 가능합니다.

public IActionResult Details(string id)
{
    var guid_id = Guid.Parse(_protector.Unprotect(id));
    var employee = _repo.GetEmployee(guid_id);
    return View(employee);
}

 

상세보기를 누르면 상세화면 이동 url 에 암호화된 id 파라미터 값이 보입니다.

 

 

 

purpose string 알아보기

 

_protector = provider.CreateProtector("EmployeesApp.EmployeesController");

 

설명한 대로 보호자 개체(protector object) 를 생성하려면 유형 IDataProtectionProvider 와 메서드의 개체가 필요합니다. 그러나 메서드 CreateProtector 메소드에서 추가 매개변수 값을 볼 수 있습니다. 해당 값을 "Purpose String" 이라고 합니다. 

 

모든 protector  에는 고유한 "purpose string" 이 있어야 하며, 암호화(cryptographic) 소비자(consumers) 간에 격리(isolation)를 제공합니다. 즉, 두 개의 IDataProtector 인스턴스(다른 "purpose string" 로 생성됨)는 서로의 페이로드(payloads) 를 읽을 수 없고 자신의 페이로드(payloads) 만 읽을 수 있습니다.

 

protector object 가 다른 protector 의 페이로드(payload) 를 읽으려고 하면 예외가 throw됩니다. 컨트롤러를 약간 수정해 보겠습니다.

 

private readonly IEmployeeRepository _repo;
private readonly IDataProtector _protector;
private readonly IDataProtector _protectorTest;
public EmployeesController(IEmployeeRepository repo, IDataProtectionProvider provider)
{
    _repo = repo;
    _protector = provider.CreateProtector("EmployeesApp.EmployeesController");
    _protectorTest = provider.CreateProtector("TestProtector");
}
public IActionResult Index()
{
    var employees = _repo.GetAll();
    foreach (var emp in employees)
    {
        var stringId = emp.Id.ToString();
        emp.EncryptedId = _protector.Protect(stringId);
    }
    var testData = _protectorTest.Protect("Test");
    var unprotectedTest = _protector.Unprotect(testData);
    return View(employees);
}

 

서로 다른 IDataProtector 인스턴스를 만들었습니다.

    _protector = provider.CreateProtector("EmployeesApp.EmployeesController");
    _protectorTest = provider.CreateProtector("TestProtector");

 

그리고 이를 암복호화를 하게 되면, 

 

    var testData = _protectorTest.Protect("Test");
    var unprotectedTest = _protector.Unprotect(testData);

아래와 같이 payload 값이 달라 오류 발생됩니다.

 

 

Data Protection 제한시간 설정하기

 

보호된 페이로드가 특정 기간 후에 만료되기를 원하는 상황이 있습니다. 이를 위해 ITimeLimitedDataProtector 인터페이스를 사용할 수 있습니다. 이 언급된 인터페이스의 인스턴스를 생성하려면 IDataProtector 의 인스턴스가 있어야 하고, 확장 메서드인 ToTimeLmitedDataProtector 으로 호출해야 합니다.

 

 

따라서 예제를 통해 시간 제한 페이로드를 생성하는 방법을 알아보겠습니다. 

2분 정도 설정을 한 다음에, 3초 후에 이를 호출하게 되면 오류가 발생됩니다.

public IActionResult Index()
{
    //Previous code removed for the example clarity
    var timeLimitedProtector = _protector.ToTimeLimitedDataProtector();
    var timeLimitedData = timeLimitedProtector.Protect("Test timed protector", lifetime: TimeSpan.FromSeconds(2));
    //just to test that this action works as long as life-time hasn't expired
    var timedUnprotectedData = timeLimitedProtector.Unprotect(timeLimitedData);
    Thread.Sleep(3000);
    var anotherTimedUnprotectTry = timeLimitedProtector.Unprotect(timeLimitedData);
    return View(employees);
}

 

만료되어 오류 발생되었습니다.

 

 

ASP.NET Core 에서 Data Protection 환경 설정 지정하기

 

 

asp.net core 에서 startup 클래스에 이를 등록할 수 있습니다. 여기서 PersistKeysToFileSystem 메소드를 사용할 수 있는데요. 이를 실행하면 해당 폴더에 xml 형태의 파이링 존재합니다.

services.AddDataProtection()
     .PersistKeysToFileSystem(new DirectoryInfo(@"bin\debug\configuration"));

 

열어보면, 생성시간, 만료시간, validation 알고리즘과 master key 값 까지 보여집니다. 하지만 key 값이 암호화 되어 있지 않아 좋은 코드는 아닙니다. 이를 개선해 보죠.

 

단지, ProtectKeysWithDpapi() 함수를 추가했는데요. 기존의 xml 파일은 삭제하고 아래 코드로 변경하고 실행하면.....

services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"bin\debug\configuration"))
    .ProtectKeysWithDpapi();

 

데이터 값이 암호화 되어 있는 것을 확인 가능합니다.

ProtectKeysWithDpapi() 함수 뿐만 아니라, 추가적인 메소드들이 몇개 더 존재합니다.

 

이제, 만료시간을 지정하는 방법도 알아보죠. 기본 시간은 90 일 입니다.  너무 길다 생각 들면 수정도 가능합니다.

SetDefaultKeyLifetime() 함수를 통해 만료시간을 10 일로 단축 하였습니다.

services.AddDataProtection()
      .PersistKeysToFileSystem(new DirectoryInfo(@"bin\debug\configuration"))
      .ProtectKeysWithDpapi()
      .SetDefaultKeyLifetime(TimeSpan.FromDays(10));

 

다시 실행해 보면, 만료 시간이 생성된 시간 기준으로 90일이 아닌 10일로 지정되어 변경된것을 확인 가능합니다.

 

 

추가적인 환경 옵션에 대해서는 아래 msdn 을 참고하시길 바랍니다.

 

https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/?view=aspnetcore-2.2 

 

Data Protection configuration in ASP.NET Core

Discover topics that explain how to configure Data Protection in ASP.NET Core.

docs.microsoft.com

 

 

변역 및 참고 사이트

 

https://code-maze.com/data-protection-aspnet-core/

 

Protecting Data with IDataProtector in ASP.NET Core - Code Maze

In this article, we are going to learn how to use IDataProtector to protect the sensitive data in the ASP.NET Core application.

code-maze.com

 

 

https://www.ttmind.com/TechPost/aspnet-core-data-protection-using-idataprotectionprovider-wi

 

ASP.NET Core Data Protection using IDataProtectionProvider With Example

ASP.NET Core Data Protection using IDataProtectionProvider With ExampleFind this Demo on GithubThis Process is easier for developers to use solid Cryptog

www.ttmind.com

 

 

EmployeesApp.zip
0.92MB