﻿# 簡介 ASP.NET Core 資料保護 (Data Protection)

ASP.NET Core Data Protection 提供了一組加密 API (cryptographic API) 來保護資料安全，其中還自動包含了密鑰管理(key management)與密鑰更換(key rotation)等機制。這套技術主要用來長期取代 .NET Framework 的 ASP.NET 1.x ~ 4.x 中 `web.config` 定義的 `<machineKey>` 元素，我研究之後發現這套機制不但簡單好上手，背後的金鑰管理也有一定程度的複雜度，可算是相當不錯的資料保護解決方案。這篇文章我想帶大家快速上手 ASP.NET Core 資料保護機制，並透過一個簡單範例說明他的加解密的用法。

![Data Protection](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/3baf2694d1d34292a3ee-234763943-5bd12775-b75d-4527-98cb-ac011986595f.jpg)

> 關於這個 ASP.NET Core 資料保護的**問題陳述**、**設計原理**、**適用對象**等重要觀念，已經被完整寫在 [ASP.NET Core Data Protection Overview](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction) 文件中，建議看英文版本，因為中文翻譯有點糟糕。

### 為什麼要用 ASP.NET Core 資料保護？

一句話表達：「我需要保存信任的資訊以供稍後擷取，但我不信任持續性機制。」

換句話說就是：「我需要儲存一些敏感的資料在應用程式端，例如帳號、密碼、金鑰、連接字串等資訊，但我不信任儲存在硬碟中的資料，因為本機磁碟中的資料有可能或有機會被駭客存取，如此一來會導致重要資料外洩。」這裡的「本機磁碟」就是「持續性機制」。

官網也用這句話來說：

> In web terms, this might be written as "I need to round-trip trusted state via an untrusted client."

我是這樣理解的：「我需要透過不受信任的用戶端來回存取信任的狀態」。這裡的**來回存取**(round-trip)意思應該是「資料平常都處於加密狀態，有需要的時候才送去一個地方做解密處理，而短暫取回的明文資料，就是 trusted state」。

為了要達到 Authenticity (真實性), Confidentiality (保密性), Isolation (隔離性) 等要求，我們縮小要求的範圍，我們可以提供進一步的約束條件。我們假設在加密系統中操作的所有服務都是平等可信的，並且數據不需要在我們直接控制的服務之外**生成**或**消費**。此外，我們要求操作盡可能快，因為每個對 Web 服務的請求可能需要通過加密系統一次或多次。這使得**對稱加密演算法**非常適合我們的情況，我們可以在需要時忽略非對稱加密法。

### 設計原則 (Design philosophy)

1.  該系統應提供簡單易用的配置方式
    
    理想情況下，系統應該是零配置的，開發人員可以立即開始使用。對於需要配置特定方面（例如密鑰庫）的情況，應考慮簡化這些特定配置。
    
2.  提供簡單易用的用戶 API (consumer-facing API)
    
    該 API 應易於正確使用，且難以誤用。
    
3.  開發人員不應該學習密鑰管理原則
    
    系統應代表開發人員處理演算法選擇和密鑰生命週期。理想情況下，開發人員甚至不應接觸到原始密鑰材料。
    
4.  在可能的情況下，應保護休息狀態的密鑰
    
    系統應找到適當的預設保護機制，並自動套用。
    

### 快速體驗 ASP.NET Core Data Protection API

1.  建立 ASP.NET Core Web API 專案
    
    ```bash
    dotnet new webapi -n AspNetCoreDataProtectionDemo
    cd AspNetCoreDataProtectionDemo
    dotnet new gitignore
    git init
    git add .
    git commit -m "Initial commit"
    ```
    
2.  安裝 Microsoft.AspNetCore.DataProtection 套件
    
    ```bash
    dotnet add package Microsoft.AspNetCore.DataProtection
    ```
    
3.  修改 `Program.cs` 註冊 Data Protection 服務到 DI 容器
    
    ```csharp
    builder.Services.AddDataProtection();
    ```
    
4.  加入一個 `DataController` API 控制器
    
    以下就是一個完整的範例，非常簡單好上手，對開發人員極度友善！
    
    其中呼叫 `CreateProtector` 時要傳入一個 [Purpose String](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/purpose-strings?view=aspnetcore-7.0) 用來區隔不同的保護資料。
    
    ```csharp
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.DataProtection;
    
    namespace AspNetCoreDataProtectionDemo.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class DataController : ControllerBase
        {
            IDataProtector _protector;
    
            public DataController(IDataProtectionProvider provider)
            {
                // 呼叫 CreateProtector 時要傳入一個 Purpose String 用來區隔不同的保護資料
                _protector = provider.CreateProtector("AspNetCoreDataProtectionDemo.v1");
            }
    
            [HttpGet("encrypt")]
            public ActionResult<String> Encrypt(string data)
            {
                return _protector.Protect(data);
            }
    
            [HttpGet("decrypt")]
            public ActionResult<String> Decrypt(string data)
            {
                return _protector.Unprotect(data);
            }
        }
    }
    ```
    
5.  啟動網站
    
    ```bash
    dotnet run --no-launch-profile
    ```
    
    > 我加上 `--no-launch-profile` 是為了能讓 ASP.NET Core 網站跑在預設的 Port 5000，方便後續測試！
    
6.  測試資料保護
    
    傳入 `Hello` 並回傳保護後的內容
    
    ```bash
    curl http://localhost:5000/api/data/encrypt?data=Hello
    ```
    
    回傳密文結果如下：
    
    ```
    CfDJ8GwUYirwodBDvAnxhXxULD96kPT9pguVgfMGeVnlcOGOrF2fyIJDb9-pwpjYopu5sEkRogzj_zv-AGpiM5lypwL6miYJEmSb3aPl39BWOPHDZ6e_LH8_WKpOougoTucqtw
    ```
    
7.  測試解除資料保護
    
    傳入密文並回傳解除保護後的內容
    
    ```bash
    curl http://localhost:5000/api/data/decrypt?data=CfDJ8GwUYirwodBDvAnxhXxULD96kPT9pguVgfMGeVnlcOGOrF2fyIJDb9-pwpjYopu5sEkRogzj_zv-AGpiM5lypwL6miYJEmSb3aPl39BWOPHDZ6e_LH8_WKpOougoTucqtw
    ```
    
    回傳結果如下：
    
    ```
    Hello
    ```
    

### 總結

如果你看的懂我上面的範例程式碼，一定會有兩個感覺：

1.  這程式碼也太簡單了吧？都不用學習各種加解密技術了嗎？
    
    沒錯，複雜的加密技術都被封裝起來了，這就是一開始**設計原則**的第一點所述，開發人員不用管這些，會呼叫 API 即可！
    
2.  咦？金鑰呢？金鑰在哪裡？不用管理嗎？我要怎樣換金鑰？
    
    有金鑰，儲存在一個神秘的地方，總之換了一台電腦金鑰就會失效，你只能在本機使用這把隱形的金鑰！
    
    但你也可以自行指定金鑰儲存的資料夾路徑，接著你就可以在多台電腦共用金鑰來進行加解密。宣告方式如下：
    
    ```csharp
    using Microsoft.AspNetCore.DataProtection;
    
    builder.Services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"));
    ```
    
    > 金鑰管理請見 [Key management in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-management) 文件說明。
    

其實 ASP.NET Core Data Protection 文件蠻多的，我還沒完全看完，但基本的加解密應用應該有辦法掌握了，金鑰的管理也有方向，建議想深入研究的朋友可以完整看完[官方文件](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction)，建立更完整的知識脈絡！👍

### 相關連結

-   本文範例程式: [https://github.com/doggy8088/AspNetCoreDataProtectionDemo](https://github.com/doggy8088/AspNetCoreDataProtectionDemo)
-   [ASP.NET Core Data Protection Overview | Microsoft Learn](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction)
    -   [ASP.NET Core資料保護概觀](https://learn.microsoft.com/zh-tw/aspnet/core/security/data-protection/introduction)
-   [Key management in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-management)
-   [NuGet Gallery | Microsoft.AspNetCore.DataProtection 7.0.5](https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection)
-   [NuGet Gallery | Microsoft.AspNetCore.DataProtection.Extensions 7.0.5](https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.Extensions)
