天天看點

.NET6之MiniAPI(十六):資料保護

作者:guisuwei

對于web,安全是一個永久的話題,是以ASP.NET Core資料保護提供了一個簡單,易用的加密API,可以用來保護資料,密鑰管理和輪換。

ASP.NET Core的資料保護是根據本機的一個key來生成加密碼,然後再用這個key來解密,如果key不一樣,解密失敗。預設情況下這個key的有效期是90天,當然這個值是可以被改變的。

預設資料保護key存放的位置,C:\Users\使用者\AppData\Local\ASP.NET\DataProtection-Keys,如:key-a2b3132b-444b-4cfa-8530-922b7e991cd9.xml,是一個xml檔案,裡面記錄了這個key的一些資訊,建立時間,激活時間,過期時間,和加解密資料所用的命名空間,加密方式等資訊,如下:

<?xml version="1.0" encoding="utf-8"?>
<key id="a2b3132b-444b-4cfa-8530-922b7e991cd9" version="1">
  <creationDate>2022-02-10T13:41:14.7492868Z</creationDate>
  <activationDate>2022-02-10T13:41:14.7421157Z</activationDate>
  <expirationDate>2022-05-11T13:41:14.7421157Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <encryptedSecret decryptorType="Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60" xmlns="http://schemas.asp.net/2015/03/dataProtection">
        <encryptedKey xmlns="">
          <!-- This key is encrypted with Windows DPAPI. -->
          <value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAAZeuIp6hZUGsfTgUuNoSSAAAAAACAAAAAAAQZgAAAAEAACAAAACpVhXOBzCWDrZmD13HwR6U3qHk7O2Pki1vPEBrtOJlrQAAAAAOgAAAAAIAACAAAAAPc2zwN10L4IVtN7tFsdQV0Cx7giHlNrWI6heArHHFt1ABAAACCCC8BhZwQ32ZZ67nBEi/tZS+HagViuS/xYtlUJOfzkJOmWg28KkBR2vM6Jo1Y1OY/AbIx6EhbGvZUkdL9aeGBy6A0GBS/3VC4/X8KV3sihRPhb1n924slVds0Y9p7J3p6sCLbhvh0ohhBe1pAENr1XkUnd2Ve4JQ0gVVgQ7vFJOonGGXGQ52dzmITzkJLYIqwNtksA31OmJUlJG7KJnrDEofKjQynvj9gXnOVnMpfHNk6v8v96WDlK7n9Ax3o/W238E7FtOVBKTNoIFWwyc40MR25IrkQdtMZ7HrODY1VRL9nuuexbVXq+5mt5QOyVgvZ1RK0sCwaBB3FSHwKmjskk+WpHXZpi3hjLx82F1gCpatSma01zDEDte0LZHRG9pVYgUXRwUaXX3G0uPuI2mXNpN57qIGZhCJC37cACIzJQ5NxuS+n9Rs6SjVykn78LBAAAAAkxOreUFzysrk5EeldARfOulsq/9OT2w/AFU9sRvPWgZPOieKcdAfIuNF09FgcyquX6IcNuydPn46Uy+saHia0w==</value>
        </encryptedKey>
      </encryptedSecret>
    </descriptor>
  </descriptor>
</key>           

使用資料保護的代碼也很簡單,注入DataProtection服務就可以,隻需要使用服務時,通過IDataProtectionProvider建立一個資料保護對象,在建立時可以添加目标字元串參數,來防隔離不同的目标字元串加密的資料,是以key和這個目标字元串都能起到隔離作用。

using Microsoft.AspNetCore.DataProtection;


var builder = WebApplication.CreateBuilder(args);
//通過SetDefaultKeyLifetime更改預設值90天
builder.Services.AddDataProtection().SetDefaultKeyLifetime(TimeSpan.FromDays(10));
var app = builder.Build();

app.MapGet("/encrypt/{str}", (IDataProtectionProvider provider, ILogger<Program> logger, string str) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var sec = protector.Protect(str);
    logger.LogInformation(sec);
    return "加密:" + sec;
});
app.MapGet("/decrypt/{sec}", (IDataProtectionProvider provider, ILogger<Program> logger, string sec) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var str = protector.Unprotect(sec);
    logger.LogInformation(str);
    return "解密:" + str;
});

app.Run();           

上面代碼隻是實作了單機部署,如果叢集部署,比如k8s中的不同pod,生成的key分别儲存在自己的pod裡,那麼外部通路又是随機配置設定的,這時就會頻繁出現解密失敗的情況,這就要集中管理key了,用redis或資料庫都可以,這裡用到的是SqlServer,首先建立存key的表,如下:

CREATE TABLE [dbo].[DataProtectionKeys1](
  [ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY ,
  [FriendlyName] [varchar](64) NULL,
  [Xml] [text] NULL
 )           

當有key儲存進來時的結果如下:

.NET6之MiniAPI(十六):資料保護

其中字段key存的值如下,與本地檔案存儲的是一樣的。

<key id="a32def14-9156-4c5e-946c-d3aa5b1a1743" version="1"> 
  <creationDate>2022-02-10T14:20:37.5680295Z</creationDate>  
  <activationDate>2022-02-10T14:20:36.5853181Z</activationDate>  
  <expirationDate>2022-05-11T14:20:36.5853181Z</expirationDate>  
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60"> 
    <descriptor> 
      <encryption algorithm="AES_256_CBC"/>  
      <validation algorithm="HMACSHA256"/>  
      <masterKey xmlns:p4="http://schemas.asp.net/2015/03/dataProtection" p4:requiresEncryption="true">  
        <!-- Warning: the key below is in an unencrypted form. -->  
        <value>gO70+leJQM8NJopS5VmMy+qkz0j+I9diBoCnxkqGxpqdXmyTULgAhMyu+3S4SJ0vjsx8Hxc+d/ipUgDBnkuQNw==</value> 
      </masterKey> 
    </descriptor> 
  </descriptor> 
</key>

           

這時,代碼需要支援EF,是以引入如下NuGet包

Microsoft.AspNetCore.DataProtection Microsoft.AspNetCore.DataProtection.EntityFrameworkCore Microsoft.EntityFrameworkCore

Microsoft.EntityFrameworkCore.SqlServer

代碼要換成EF方式持久化key,要注入EF的Context,然後注入資料保護對象時指明持久化的方式:

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<DataProtContext>(options =>
      options.UseSqlServer(builder.Configuration.GetConnectionString("DataProtDB")));
builder.Services.AddDataProtection().PersistKeysToDbContext<DataProtContext>();
var app = builder.Build();

app.MapGet("/encrypt/{str}", (IDataProtectionProvider provider, ILogger<Program> logger, string str) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var sec = protector.Protect(str);
    logger.LogInformation(sec);
    return "加密:" + sec;
});
app.MapGet("/decrypt/{sec}", (IDataProtectionProvider provider, ILogger<Program> logger, string sec) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var str = protector.Unprotect(sec);
    logger.LogInformation(str);
    return "解密:" + str;
});

app.Run();

class DataProtContext : DbContext, IDataProtectionKeyContext
{
    public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }


    public DataProtContext(DbContextOptions<DataProtContext> options)
          : base(options)
    {}
}           

加密結果:

.NET6之MiniAPI(十六):資料保護

解密結果:

.NET6之MiniAPI(十六):資料保護

如果生成的加密串需要時效性,需要把生成的Protector轉成TimeLimitedDataProtector來加解密,如下:

……
app.MapGet("/encrypt/{str}", (IDataProtectionProvider provider, ILogger<Program> logger, string str) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var sec = protector.ToTimeLimitedDataProtector().Protect(str, TimeSpan.FromSeconds(30));
    logger.LogInformation(sec);
    return "加密:" + sec;
});
app.MapGet("/decrypt/{sec}", (IDataProtectionProvider provider, ILogger<Program> logger, string sec) =>
{
    var protector = provider.CreateProtector("a.b.c");
    var str = protector.ToTimeLimitedDataProtector().Unprotect(sec);
    logger.LogInformation(str);
    return "解密:" + str;
});

……
           

如果加密串過期送出,報錯如下:

.NET6之MiniAPI(十六):資料保護

繼續閱讀