Net6使用AES加解密
- 一、AES加密(在此类EncryptionUtil中)
- 二、AES解密(在此类EncryptionUtil中)
- 三、结合Filter和Attribute通过AOP实现加解密
- 1.创建加解密接口
- 2.密钥和向量配置类和配置信息
- 3.解密filter
- 4.加密filter
- 5.DynamicSetProperty方法扩展类
- 6.标记加密的属性
- 7.AjaxResponse
- 四、AOP的使用
- Swagger请求加解密展示
一、AES加密(在此类EncryptionUtil中)
public static string EncryptAES(string plainText, string passPhrase, string iv)
{
if (plainText == null)
{
return null;
}
if (passPhrase == null)
{
return null;
}
if (iv == null)
{
return null;
}
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
var keyBytes = System.Text.Encoding.UTF8.GetBytes(passPhrase);
var ivBytes = System.Text.Encoding.UTF8.GetBytes(iv);
using (var symmetricKey = Aes.Create())
{
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivBytes))
{
// 加密后的输出流
using (var memoryStream = new MemoryStream())
{
// 将加密后的目标流(encryptStream)与加密转换(encryptTransform)相连接
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
// 将一个字节序列写入当前 CryptoStream (完成加密的过程)
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = memoryStream.ToArray();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
二、AES解密(在此类EncryptionUtil中)
public static string DecryptAES(string cipherText, string passPhrase, string iv)
{
if (string.IsNullOrEmpty(cipherText))
{
return null;
}
if (passPhrase == null)
{
return null;
}
if (iv == null)
{
return null;
}
var cipherTextBytes = Convert.FromBase64String(cipherText);
var keyBytes = System.Text.Encoding.UTF8.GetBytes(passPhrase);
var ivBytes = System.Text.Encoding.UTF8.GetBytes(iv);
using (var symmetricKey = Aes.Create())
{
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
// 用当前的 Key 属性和初始化向量 IV 创建对称解密器对象
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivBytes))
{
// 解密后的输出流
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
解密后的输出流
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader sReader = new StreamReader(cryptoStream))
{
return sReader.ReadToEnd();
//return System.Text.Encoding.UTF8.GetString(plainTextBytes, plainTextBytes.Length);
}
}
解密后的输出流(异常方法,请使用上面的方案)
//using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
//{
// var plainTextBytes = new byte[cipherTextBytes.Length];
// var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
// return System.Text.Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
//}
}
}
}
}
三、结合Filter和Attribute通过AOP实现加解密
1.创建加解密接口
接口
public interface IStringEncryptionService
{
//加密
string Encrypt(string plainText, string passPhrase = null, string iv = null);
//解密
string Decrypt(string cipherText, string passPhrase = null, string iv = null);
}
实现
public class StringEncryptionService : IStringEncryptionService
{
//加密的密钥和偏移向量的配置类
private readonly StringEncryptionOption _options;
public StringEncryptionService(IOptionsMonitor<StringEncryptionOption> optionsMonitor)
=> _options = optionsMonitor.CurrentValue;
public virtual string Decrypt(string cipherText, string passPhrase = null, string iv = null)
{
if (string.IsNullOrEmpty(cipherText))
{
return null;
}
if (passPhrase == null)
{
passPhrase = _options.SecretParameterEncryptKey;
}
if (iv == null)
{
iv = _options.SecretParameterIv;
}
//调用第一部分的函数
return EncryptionUtil.DecryptAES(cipherText, passPhrase, iv);
}
public virtual string Encrypt(string plainText, string passPhrase = null, string iv = null)
{
if (plainText == null)
{
return null;
}
if (passPhrase == null)
{
passPhrase = _options.SecretParameterEncryptKey;
}
if (iv == null)
{
iv = _options.SecretParameterIv;
}
//调用第一部分的函数
return EncryptionUtil.EncryptAES(plainText, passPhrase, iv);
}
}
2.密钥和向量配置类和配置信息
public class StringEncryptionOption
{
/// <summary>
/// 私密参数加密KEY
/// </summary>
public string SecretParameterEncryptKey { get; set; }
/// <summary>
/// 私密参数IV
/// </summary>
public string SecretParameterIv { get; set; }
}
配置信息
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"StringEncryptionOption": {
"SecretParameterEncryptKey": "1234#567qwerdfas",(16位)
"SecretParameterIv": "1234567891011121"
}
}
Program.cs 配置信息
//添加配置
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.Development.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables().Build();
//注入配置类
builder.Services.Configure<StringEncryptionOption>(configuration.GetSection(nameof(StringEncryptionOption)));
builder.Services.AddScoped<IStringEncryptionService, StringEncryptionService>();
3.解密filter
public class RequestDecryptAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionArguments.Count <= 0) return;
//【HttpContext 不是线程安全型。 在处理请求之外读取或写入 HttpContext 的属性可能会导致 NullReferenceException。】
// 其实这个也很好理解,http请求中的参数只允许最终的执行操作方法来获取,也就是说只允许被获取一次
// 。所以在这里想直接对HttpContext中的参数进行读取并修改是行不通的,但微软官方帮我们封装了一下,
// 看一下ActionExecutingContext 以及ActionContext
// ActionExecutingContext里面有一个ActionArguments,是一个字典,
// 这就是http请求中的真实参数,而参数名称则在ActionContext.ActionDescriptor.Parameters中
var request = context.ActionArguments.First().Value;
if (request == null)
{
return;
}
var service = context.HttpContext.RequestServices
.GetService(typeof(IStringEncryptionService)) as IStringEncryptionService;
if (service == null)
{
return;
}
request.DynamicSetPropertyValue<RequestDataSecurityAttribute>((obj, prop) =>
{
var value = prop.GetValue(obj);
if (value == null) return;
var decryptValue = service.Decrypt(value.ToString());
prop.SetValue(obj, decryptValue, null);
});
}
}
4.加密filter
public class RequestEncryptAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var response = context.Result as ObjectResult;
var result = (AjaxResponse<object>)response.Value ;
var service = context.HttpContext.RequestServices
.GetService(typeof(IStringEncryptionService)) as IStringEncryptionService;
if (service == null)
{
return;
}
//使用委托来对数据进行加密操作
result.Data.DynamicSetPropertyValue<RequestDataSecurityAttribute>((obj, prop) =>
{
//获得获得obj里prop的值
var value = prop.GetValue(obj);
//不空说明有需要加解密的数据
if (value == null) return;
//加解密数据
var decryptValue = service.Encrypt(value.ToString());
//设置原本未加密的值为已生成的加密数据
prop.SetValue(obj, decryptValue, null);
});
context.Result = new ObjectResult(result);
}
}
5.DynamicSetProperty方法扩展类
public static class GenericTypeExtensions
{
/// <summary>
/// 需加解密处理的请求响应类型缓存字典
/// 记录的是key(数据类型type),value(那些属性数据需要进行加解密)
/// </summary>
private static readonly ConcurrentDictionary<Type, List<PropertyInfo>> securityRequestArgDic = new ConcurrentDictionary<Type, List<PropertyInfo>>();
public static void DynamicSetPropertyValue<TAttribute>(this object @object, Action<object, PropertyInfo> action)
{
//获得Result数据类型type
var argType = @object.GetType();
//优先从缓存查找并处理待加解密请求响应类型
if (securityRequestArgDic.TryGetValue(argType, out var argProperties))
{
//如果字典里有对应的object,那么根据value的属性数据,直接对相应的属性加解密,不用一步步循环判断
foreach (var property in argProperties)
{
action(@object, property);
}
return;
}
//获得传入的Result类型的所有属性
var props = @object.GetType().GetProperties();
//生成一个空集合,用于初始化集合
var addedArgProperties = Enumerable.Empty<PropertyInfo>().ToList();
//遍历传入的Result类型所有属性
foreach (var prop in props)
{
//获得属性上有指定特性的属性
var attrs = prop.GetCustomAttributes(typeof(TAttribute), false);
//如果为0,则说明没找到这个类型
if (attrs.Length <= 0)
{
continue;
}
//添加入字典作为缓存,下次进来,就有这次Result类型的缓存了。
addedArgProperties.Add(prop);
//执行加解密操作
action(@object, prop);
}
if (addedArgProperties.Any())
securityRequestArgDic.AddOrUpdate(argType, type => addedArgProperties, (type, oldValue) => addedArgProperties);
}
}
6.标记加密的属性
public class RequestDataSecurityAttribute : Attribute
{
}
7.AjaxResponse
public class AjaxResponse<T>
{
public AjaxResponse(T data)
{
Data = data;
}
public T Data { get; set; }
}
四、AOP的使用
AESController.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace AES.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AesController : ControllerBase
{
/// <summary>
/// 解密
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
[HttpPost]
[Route("decry")]
//这里对应上面加解密的filter,在方法前对参数进行设置,同时也标注了,对该参数进行加密或者解密操作
[RequestDecrypt]
public AjaxResponse<object> Decry(Student student)
{
var ajaxResonse = new AjaxResponse<object>(student);
return ajaxResonse;
}
/// <summary>
/// 加密
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
[HttpPost]
[Route("encrypt")]
//这里对应上面加解密的filter,在方法前对参数进行设置,同时也标注了,对该参数进行加密或者解密操作
[RequestEncrypt]
public AjaxResponse<object> Encrypt(Student student)
{
var ajaxResonse = new AjaxResponse<object>(student);
return ajaxResonse;
}
}
}
Student.cs
public class Student
{
public long Id { get; set; }
//用来标注这个属性在filter里需要进行加解密
[RequestDataSecurity]
public string? CardNo { get; set; }
}
Swagger请求加解密展示
加密结果解密请求体解密结果