Net6使用AES加解密

详解

一、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请求加解密展示

在这里插入图片描述

加密结果
在这里插入图片描述

解密请求体
在这里插入图片描述
解密结果
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有诗亦有远方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值