【.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