【.NET Core框架】数据保护(Data Protection)

介绍

ASP.NET Core 数据保护提供简单易用的加密 API,开发人员可以使用它来保护数据,包括密钥管理。
Data Protection(数据安全)机制:为了确保Web应用敏感数据的安全存储,该机制提供了一个简单、基于非对称加密改进的加密API用于数据保护。
它不需要开发人员自行生成密钥,它会根据当前应用的运行环境,生成该应用独有的一个私钥。

相关Nuget包

Microsoft.AspNetCore.DataProtection.Abstractions
Microsoft.AspNetCore.DataProtection
Mcrosoft.AspNetCore.DataProtection.Extensions

快速开始

注册服务

builder.Services.AddDataProtection();

加解密

public string Get([FromServices] IDataProtectionProvider provider)
{
    //获取IDataProtector方式1
    IDataProtector dataProtector = HttpContext.RequestServices.GetDataProtector("myapp");
    //获取IDataProtector方式2
    IDataProtector dataProtector2 = provider.CreateProtector("myapp");
    //加密
    string protectText = dataProtector2.Protect("abc");
    //解密
    var text = dataProtector2.Unprotect(protectText);
}

重要接口

IDataProtectionProvider

用来创建IDataProtector purpose可以理解为一个标识,指示当前Protector的用途。

public interface IDataProtectionProvider
{

    IDataProtector CreateProtector(string purpose);
}

IDataProtector

IDataProtector继承自IDataProtectionProvider ,并且提供了两个方法 Protect Unprotect ,从命名来看,一个是加密,一个是解密。

public interface IDataProtector : IDataProtectionProvider
{

    byte[] Protect(byte[] plaintext);

    byte[] Unprotect(byte[] protectedData);
}

扩展方法DataProtectionCommonExtensions

public static class DataProtectionCommonExtensions
{
    public static IDataProtector CreateProtector(this IDataProtectionProvider provider, IEnumerable<string> purposes);

    public static IDataProtector CreateProtector(this IDataProtectionProvider provider, string purpose, params string[] subPurposes);

    public static IDataProtector GetDataProtector(this IServiceProvider services, IEnumerable<string> purposes);

    public static IDataProtector GetDataProtector(this IServiceProvider services, string purpose, params string[] subPurposes);

    public static string Protect(this IDataProtector protector, string plaintext);

    public static string Unprotect(this IDataProtector protector, string protectedData);
}

DataProtector是有层次结构的,再看一下IDataProtector接口,它自身也实现了IDataProtectionProvider接口,就是说IDataProtector自身也可以再创建IDataProtector
CreateProtector([ "myapp", "order" ])相当于provider.CreateProtector("myapp").CreateProtector("order")

ITimeLimitedDataProtector

具有过期时间的加密字符串

Protect(byte[] plaintext, DateTimeOffset expiration) 
Protect(byte[] plaintext, TimeSpan lifetime) 
Protect(byte[] plaintext)
Protect(string plaintext, DateTimeOffset expiration)
Protect(string plaintext, TimeSpan lifetime) 
Protect(string plaintext) 

配置

应用名:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetApplicationName("my application");
}

私钥过期时间:
Data Protection 的默认保存时间是90天,你可以通过以下方式来修改默认的保存时间:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}

修改加密算法:

services.AddDataProtection()
    .UseCryptographicAlgorithms(new AuthenticatedEncryptionSettings()
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

私钥共享、持久化

如果密钥不持久化,每次启动都会生成临时密钥,之前加密的密文解密失败,导致用户退出登录等问题。可以将密钥保存到磁盘(单体应用)、redis、mysql等

IXmlRepository

IXmlRepository接口主要提供了持久化以及检索XML的方法,它只要提供了两个API:

IReadOnlyCollection GetAllElements()
StoreElement(XElement element, string friendlyName)

保存到磁盘

string keysPath = Path.Combine(builder.Environment.ContentRootPath, "keys");
builder.Services.AddDataProtection()
     .PersistKeysToFileSystem(new DirectoryInfo(keysPath));

保存到mysql

1、创建数据表

SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `dataprotectionkeys`;
CREATE TABLE `dataprotectionkeys` (
    `FriendlyName` varchar(100) NOT NULL,
    `XmlData` varchar(2000) DEFAULT NULL,
    PRIMARY KEY (`FriendlyName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2、创建服务MySqlDBXmlRepository

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.Repositories;
using MySqlConnector;
using System.Data;
using System.Text;
using System.Xml.Linq;

    /// <summary>
    /// MySql存储用户登录加密用到的Persist Keys,重启程序不影响已登录的用户
    /// </summary>
    public class MySqlDBXmlRepository : IXmlRepository
    {
        private string _connectionString = null;
        private readonly ILogger _logger;

        public MySqlDBXmlRepository(IConfiguration configuration, ILoggerFactory loggerFactory)
        {
            this._connectionString = configuration.GetConnectionString("DefaultConnection");
            this._logger = loggerFactory.CreateLogger<MySqlDBXmlRepository>();
        }

        public IReadOnlyCollection<XElement> GetAllElements()
        {
            List<XElement> xElementList = new List<XElement>();
            using (MySqlConnection connection = new MySqlConnection(this._connectionString))
            {
                connection.Open();
                using (MySqlCommand command = new MySqlCommand("SELECT * FROM `dataprotectionkeys`", connection))
                {
                    using (MySqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        while (reader.Read())
                        {
                            if (reader[1] != null && reader[1] != DBNull.Value)
                            {
                                xElementList.Add(XElement.Parse(reader[1].ToString()));
                            }
                        }
                    }
                }
            }
            return xElementList.AsReadOnly();
        }

        public void StoreElement(XElement element, string friendlyName)
        {
            string xmlData = element.ToString(SaveOptions.DisableFormatting);
            using (MySqlConnection connection = new MySqlConnection(this._connectionString))
            {
                connection.Open();
                using (MySqlCommand command = new MySqlCommand())
                {
                    bool update = false;
                    command.Connection = connection;
                    command.CommandText = "SELECT * FROM `dataprotectionkeys`";
                    using (MySqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            if (string.Equals(reader[0], friendlyName))
                            {
                                update = true;
                                break;
                            }
                        }
                    }
                    if (update)
                    {
                        command.CommandText = string.Format("UPDATE `dataprotectionkeys` SET `XmlData`='{0}' WHERE `FriendlyName`='{1}'", xmlData, friendlyName);
                    }
                    else
                    {
                        command.CommandText = string.Format("INSERT INTO `dataprotectionkeys` VALUES('{0}','{1}')", friendlyName, xmlData);
                    }
                    command.ExecuteNonQuery();
                }
            }
        }
    }

3、扩展方法

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
public static class DataProtectionEx
{

    /// <summary>
    /// 添加MySql方式存放Persist Key(调用此方法需要先把DefaultConnection连接字符串设置好)
    /// PersistKeysToFileSystem => FileSystemXmlRepository
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static IServiceCollection PersistKeysToMySql(this IServiceCollection services, IConfiguration configuration)
    {
        //③MySql存储用户登录加密用到的Persist Keys,重启程序不影响已登录的用户
        return services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(arg =>
        {
            var loggerFactory = arg.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
            return new ConfigureOptions<KeyManagementOptions>(options =>
            {
                options.XmlRepository = new MySqlDBXmlRepository(configuration, loggerFactory);
            });
        });
    }
}

4、注册服务

builder.Services.PersistKeysToMySql(this.Configuration);

5、连接字符串

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=xxxx;User Id=root;Password=xxx;Database=xxx;Port=3306;"
   }
}

保存到redis

1、添加Nuget

Install-package Microsoft.Extensions.Caching.StackExchangeRedis
Install-package Microsoft.AspNetCore.DataProtection.StackExchangeRedis

2、注册服务

    public void ConfigureServices(IServiceCollection services)
        {
            ConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect("xxxxxx:6379,defaultDatabase=10,password=xxxxxxx");
            string applicationName = "FAN.APP";
            services.AddDataProtection(o =>
            {
                o.ApplicationDiscriminator = applicationName;
            })
            .PersistKeysToStackExchangeRedis(connectionMultiplexer, "FAN_share_key");//秘钥存储到Redis中


        }

参考:
https://docs.microsoft.com/zh-cn/aspnet/core/security/data-protection/introduction?view=aspnetcore-3.1
https://www.cnblogs.com/lwqlun/p/9726191.html
https://www.cnblogs.com/savorboard/p/dotnetcore-data-protection.html

posted @ 2022-06-12 19:55  .Neterr  阅读(1665)  评论(0编辑  收藏  举报