ASP.NET Core
数据保护提供简单易用的加密 API,开发人员可以使用它来保护数据,包括密钥管理。
Data Protection
(数据安全)机制:为了确保Web应用敏感数据的安全存储,该机制提供了一个简单、基于非对称加密改进的加密API用于数据保护。
它不需要开发人员自行生成密钥,它会根据当前应用的运行环境,生成该应用独有的一个私钥。
注册服务
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); }
ConfigureService()方法添加数据保护服务:
string applicationName = $"FAN.APP"; //添加数据保护服务,设置统一应用程序名称和加密方式 IDataProtectionBuilder dataProtectionBuilder = services .AddDataProtection(options => options.ApplicationDiscriminator = applicationName) .SetApplicationName(applicationName) .SetDefaultKeyLifetime(TimeSpan.FromDays(7))//<expirationDate>最小7天</expirationDate> .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration { EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, ValidationAlgorithm = ValidationAlgorithm.HMACSHA512 });
用来创建IDataProtector
,purpose
可以理解为一个标识,指示当前Protector的用途。
public interface IDataProtectionProvider { IDataProtector CreateProtector(string purpose); }
IDataProtector
继承自IDataProtectionProvider
,并且提供了两个方法 Protect
和 Unprotect
,从命名来看,一个是加密,一个是解密。
public interface IDataProtector : IDataProtectionProvider { byte[] Protect(byte[] plaintext); byte[] Unprotect(byte[] protectedData); }
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")
具有过期时间的加密字符串
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
接口主要提供了持久化以及检索XML的方法,它只要提供了两个API:
GetAllElements() : IReadOnlyCollection StoreElement(XElement element, string friendlyName)
string keysPath = Path.Combine(builder.Environment.ContentRootPath, "keys"); builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(keysPath));
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;" } }
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