WebApi接口签名加密和验签

  • 业务场景需求
  • 生成RSA密钥对
  • 报文根据规则进行排序
  • 用请求方的私钥对报文签名
  • 用接收方的公钥对报文加密
  • 发送密文给接收方,用接收方的密钥解密
  • 接收方对数据验签
  • 关于Convert.ToBase64String(Byte[])和Encoding.UTF8.GetString(Byte[])的区别

业务场景需求

现有请求方A,与接收方B,以下是请求方的操作:

以下是请求方的操作:
假设 A 传输的内容为 Mark
1.组装报文,如: RSA_Mark (按照消息头_非业务参数_业务参数排序),使用固定的消息头 RSA 方便知道对方解密成功
2.用 A的私钥 对报文 RSA_Mark 签名,假设签名结果为 XJ9B5D1
3.把签名结果组装在原报文末尾,如:RSA_Mark_XJ9B5D1
4.用 B的公钥 对报文 RSA_Mark_XJ9B5D1 加密,结果假设为: NE03WBEN12=
5.将加密结果 NE03WBEN12= 发送给 B

以下是接收方的操作:
1.接到密文 NE03WBEN12=
2.用 B的私钥 进行解密,得到:RSA_Mark_XJ9B5D1
3.检验报文消息头是否为 RSA ,以检验是否是用 B的公钥 进行加密
4.解密成功后, 截取签名 消息尾得到: XJ9B5D1
5.用 A的公钥消息体 进行验签,待验证的消息体为 RSA_Mark ,签名值 XJ9B5D1
6.若成功验签,贼说明该消息来自 A 合法数据

(注:报文排序规则,根据非业务参数和业务参数拼接字符串并按照首字母排序,如果首字母相同,则按照第二个字母排序,以此类推
如:rp_13510103189_1540803537222_1_1540803537_10
: rp_{mobile.value}{sn.value}{source.value}{timestamp.value}{ua.value}

生成RSA密钥对

private const int RsaKeySize = 1024;        //要使用的密钥的大小(以位为单位)
private const string publicKeyFileName = "ServerRSA.Pub";         //公钥
private const string privateKeyFileName = "ServerRSA.Private";    //私钥
private static string basePathToStoreKeys = ConfigurationManager.AppSettings["basePathToStoreServerKeys"];   //从配置文件中读取密钥存放路径public static string GenerateKeys(){string path = basePathToStoreKeys;using (var rsa = new RSACryptoServiceProvider(RsaKeySize)){try{// 获取私钥和公钥。var publicKey = rsa.ToXmlString(false);var privateKey = rsa.ToXmlString(true);if (!Directory.Exists(path)){Directory.CreateDirectory(path);}bool result = false;string resultMsg = "该路径已存在密钥对,生成失败";// 保存到磁盘if (!File.Exists(Path.Combine(path, publicKeyFileName))){File.WriteAllText(Path.Combine(path, publicKeyFileName), publicKey);result = true;}if (!File.Exists(Path.Combine(path, privateKeyFileName))){File.WriteAllText(Path.Combine(path, privateKeyFileName), privateKey);result = true;}if (result){resultMsg = string.Format("生成的RSA密钥对的路径: {0}\\ [{1}, {2}]", path, publicKeyFileName, privateKeyFileName);}return resultMsg;}finally{rsa.PersistKeyInCsp = false;}}}

报文根据规则进行排序

这里的方法是把对象转为字典,然后对Key值进行升序输出字符串

public static Dictionary<string, object> ObjConvertDic(Dictionary<string, object> dic, T obj){//判空if (obj == null){return dic;}Type t = obj.GetType(); // 获取对象对应的类, 对应的类型PropertyInfo[] pi = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 获取当前type公共属性string dickeyname = string.Empty;   //用于存储 表名+字段名foreach (PropertyInfo p in pi){MethodInfo m = p.GetGetMethod();if (m != null && m.IsPublic){dickeyname = t.Name + "_" + p.Name;// 进行判NULL处理 以及 重复键处理if (m.Invoke(obj, new object[] { }) != null && !dic.ContainsKey(dickeyname)){dic.Add(dickeyname, m.Invoke(obj, new object[] { })); // 向字典添加元素}}}return dic;}/// <summary>/// 字典中将key值进行升序排序,并将对应的value值拼接为字符串输出/// </summary>/// <param name="dic"></param>/// <returns></returns>public static string DicSortToString(Dictionary<string, object> dic){//根据字典键升序var ascDic = from objDic in dic orderby objDic.Key ascending select objDic;//报文消息头string str = ConfigurationManager.AppSettings["messageHeader"];foreach (var item in ascDic){str += "_" + item.Value;}return str;}

用请求方的私钥对报文签名

public static string privateToSign(string str){//判空if(string.IsNullOrEmpty(str)){return null;}//要签名文本编码为base64byte[] hashByteSignture = System.Text.Encoding.Unicode.GetBytes(str);//加载私钥RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();var privateXmlKey = File.ReadAllText(Path.Combine(basePathToStoreKeys, privateKeyFileName));rsa.FromXmlString(privateXmlKey);//哈希算法:SHA1(160bit)、SHA256(256bit)、MD5(128bit)byte[] sign = rsa.SignData(hashByteSignture, CryptoConfig.MapNameToOID("SHA1"));return Convert.ToBase64String(sign);}

用接收方的公钥对报文加密

将生成的签名追加到原报文尾部得到新报文
这里加密使用了分段加密,因为1024位的证书,加密时最大支持117个字节,解密时为128;2048位的证书,加密时最大支持245个字节,解密时为256。

/// <summary>/// 用给定路径的RSA公钥文件加密纯文本。/// </summary>/// <param name="plainText">要加密的文本</param>/// <param name="pathToPublicKey">用于加密的公钥路径.</param>/// <returns>表示加密数据的64位编码字符串.</returns>private static string Encrypt(string plainText, string pathToPublicKey){using (var rsa = new RSACryptoServiceProvider(RsaKeySize)){try{//加载公钥读取xmlstring publicXmlKey = File.ReadAllText(pathToPublicKey);rsa.FromXmlString(publicXmlKey);byte[] bytesToEncrypt = Encoding.Unicode.GetBytes(plainText);//分段加密 int keySize = rsa.KeySize / 8;int bufferSize = keySize - 11;byte[] buffer = new byte[bufferSize];//内存流,为系统内存提供读写操作MemoryStream msInput = new MemoryStream(bytesToEncrypt);MemoryStream msOuput = new MemoryStream();int readLen = msInput.Read(buffer, 0, bufferSize);while (readLen > 0){byte[] dataToEnc = new byte[readLen];Array.Copy(buffer, 0, dataToEnc, 0, readLen);//加密  使用从缓冲区读取的数据将字节块写入当前流byte[] encData = rsa.Encrypt(dataToEnc, false);msOuput.Write(encData, 0, encData.Length);readLen = msInput.Read(buffer, 0, bufferSize);}msInput.Close();byte[] result = msOuput.ToArray();    //得到加密结果msOuput.Close();//var bytesEncrypted = rsa.Encrypt(bytesToEncrypt, false);return Convert.ToBase64String(result);}finally{rsa.PersistKeyInCsp = false;}}}

发送密文给接收方,用接收方的密钥解密

注:非业务参数使用HTTP请求的header头解析。

/// <summary>/// Decrypts encrypted text given a RSA private key file path.给定路径的RSA私钥文件解密 加密文本/// </summary>/// <param name="encryptedText">密文</param>/// <param name="pathToPrivateKey">用于解密的私钥路径.</param>/// <returns>未加密数据的字符串</returns>private static string Decrypt(string encryptedText, string pathToPrivateKey){using (var rsa = new RSACryptoServiceProvider(RsaKeySize)){try{//加载私钥string privateXmlKey = File.ReadAllText(pathToPrivateKey);rsa.FromXmlString(privateXmlKey);byte[] bytesEncrypted = Convert.FromBase64String(encryptedText);//分段解密int keySize = rsa.KeySize / 8;byte[] buffer = new byte[keySize];//内存流,为系统内存提供读写操作MemoryStream msInput = new MemoryStream(bytesEncrypted);MemoryStream msOuput = new MemoryStream();int readLen = msInput.Read(buffer, 0, keySize);while (readLen > 0){byte[] dataToDec = new byte[readLen];Array.Copy(buffer, 0, dataToDec, 0, readLen);//解密    使用从缓冲区读取的数据将字节块写入当前流byte[] encData = rsa.Decrypt(dataToDec, false);msOuput.Write(encData, 0, encData.Length);readLen = msInput.Read(buffer, 0, keySize);}//关闭内存流msInput.Close();byte[] result = msOuput.ToArray();    //得到解密结果msOuput.Close();return System.Text.Encoding.Unicode.GetString(result);}catch (Exception ex){return ex.ToString();}finally{rsa.PersistKeyInCsp = false;}}}

接收方对数据验签

/// <summary>/// 验签/// </summary>/// <param name="sign"></param>/// <returns></returns>public static string CheckSign(string message){//获取非业务参数header对象的长度HeadersInfo headersInfo = new HeadersInfo();PropertyInfo[] propertyInfo = headersInfo.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);//截取第一个下划线'_'前的文本为消息头,最后一个下划线'_'后的文本为签名string[] list = message.Split('_');//判断长度是否合法if (list.Length < propertyInfo.Length){return UtilityEnum.InspectionResult.Invalid.ToString();}string messageHeader = list[0];string timestamp = list[1];//要验证的签名数据string signature = list[list.Length - 1];byte[] hashByteSignature = Convert.FromBase64String(signature);//查看消息头是否正确if (messageHeader != ConfigurationManager.AppSettings["messageHeader"]){return UtilityEnum.InspectionResult.Invalid.ToString();}//文本截取签名(含下划线'_')后,是已签名的数据string buffer = message.Substring(0, message.Length - signature.Length - 1);byte[] fromBase64Buffer = Encoding.Unicode.GetBytes(buffer);//加载发送方的公钥进行验签var rsa = new RSACryptoServiceProvider();var publicXmlKey = File.ReadAllText(Path.Combine(ConfigurationManager.AppSettings["basePathToStoreClientKeys"], "ClientRSA.Pub"));rsa.FromXmlString(publicXmlKey);//MD5 mD5 = new MD5CryptoServiceProvider();//rsa.VerifyData(hashByteSignature, mD5, Convert.FromBase64String(buffer));//rsa.VerifyData(hashByteSignature, CryptoConfig.MapNameToOID("MD5"), Convert.FromBase64String(buffer));//哈希算法:SHA1(160bit)、SHA256(256bit)、MD5(128bit)if (rsa.VerifyData(fromBase64Buffer, CryptoConfig.MapNameToOID("SHA1"), hashByteSignature)){//判断timestamp是否超时if (UtilityHelper.IsTimestampValidity(timestamp)){return UtilityEnum.InspectionResult.Timeout.ToString();}}else{return UtilityEnum.InspectionResult.Invalid.ToString();}return UtilityEnum.InspectionResult.Validity.ToString();}

关于Convert.ToBase64String(Byte[])和Encoding.UTF8.GetString(Byte[])的区别

对加密方法返回的byte[],用Convert.ToBase64String
对普通的文字操作,用Encoding.UTF8.GetBytes()

Encoding.UTF8.GetString是针对使用utf8编码得到的字符串对应的byte[]使用,可以还原我们能看懂的字符串
而Convert.ToBase64String是对任意byte[]都可使用,得到的是用字符串表示的byte[]信息 内容类似"QJ5/EYPtJPZ5Inyv="

如果一个地方用Convert.ToBase64String来操作byte[]获得string,而另一个地方要用相同的byte[],最好对应用 Convert.FromBase64String(string);

一般 Base64 用于转格式,如:图片。
Encoding 用于转换编码,如:文字(普通文字不是Base64 编码)

以上。
如有不合理的地方或更好的建议,请不吝赐教,谢谢!

.NET WebApi实现RSA加密与解密,签名与验签相关推荐

  1. ios rsa加密 java解密_iOS RSA加密与解密 签名与验签(附Java端处理)

    本篇文章将涉及以下几个操作: 1.iOS端使用RSA公钥加密,iOS端使用RSA私钥解密. 2.iOS端使用RSA私钥加签,iOS端使用RSA公钥验签. 3.iOS端使用RSA公钥加密,Java端使用 ...

  2. 使用RSA、MD5对参数生成签名与验签

    在日常的工作中,我们对外提供的接口或调用三方的接口往往有一步生成签名或验签的步骤,这个步骤主要是验证调用方是 不是合法的以及内容是否被修改.比如:对于某些网上公开下载的软件,视频,尤其是镜像文件.如果 ...

  3. 跨语言平台的RSA加密、解密、签名、验证算法的实现

      在网上可以找到各种各样的RSA实现代码,原理都是RSA算法的基本原理,但是在处理数据块划分.填充等问题上都是形形色色的,本文旨在探讨.实现遵循RFC 2313 PKCS#1 v1.5标准的一种跨语 ...

  4. python RSA加密、解密、签名

    python中用于RSA加解密的库有好久个,本文主要讲解rsa.M2Crypto.Crypto这三个库对于RSA加密.解密.签名.验签的知识点. 知识基础 加密是为了保证传输内容隐私,签名是为了保证消 ...

  5. asp版 vbscript RSA公钥加密 / 私钥解密 / 私钥签名 / 公钥验签(支持中文)分段加密解密

    最近有空在把自己的asp站点后端函数全部整理了下,在弄RSA的时候遇到了坑了,然后找到下面这位兄弟刚好发布的文章: https://blog.csdn.net/todaygods/article/de ...

  6. 分享一个RSA加解密工具类,公钥加密私钥解密、私钥加密公钥解密、私钥签名公钥验签、生成公钥私钥

    测试: public static void main(String[] args) {try {//生成公钥私钥Map<String, Object> map = RSAUtil.ini ...

  7. RSA加密、解密、签名、验签(验证签名)RSA算法原理

    转载链接:https://www.jianshu.com/p/8dc4a5f64e06 https://www.cnblogs.com/pcheng/p/9629621.html RSA原理:http ...

  8. 微信小程序-RSA签名、验签、加密、解密

    title: [小程序]RSA签名 type: categories date: 2017-05-27 17:01:15 categories: 小程序 tags: [RSA, 签名] 一个适用于微信 ...

  9. 数字证书应用综合揭秘(包括证书生成、加密、解密、签名、验签)

    引言 数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件.为现实网络安全化标准如今大部分的 B2B.B2C.P2P.O2O 等商业网站含有重要企业资料个人资料的信息资信网站 ...

最新文章

  1. JS学习笔记 - 代码复用
  2. 5笔涂出一只3D猫咪模型,可跑可跳无需手动绑定骨骼,新鬼畜素材get丨浙大开源...
  3. 3.3.2 Rhino与env.js
  4. Ubuntu16.04安装ROS kinetic 及问题解决
  5. Redis高级特性及优化
  6. WPF Binding
  7. vue介绍及环境安装
  8. 推荐系统--矩阵分解(1)
  9. 算法篇(暂时就接触一个)
  10. cmdb python 采集虚拟机_Python编程(三十四):CMDB后台管理、封装自定义JS组件、前端td标签定制显示内容及属性...
  11. java面试排序_Java-四种面试常考排序
  12. MS SQL 数据库状态为SUSPECT的处理方法
  13. ddwrt 扩张linux分区,『2010.8.9』DDWRT脱机下载+架设网站修正加强版tar.gz模式发布(修复下载地址)...
  14. WdatePicker日历控件用法
  15. DxO FilmPack 5详细教程:对照片进行艺术渲染,使其具有专业电影的色彩和颗粒感
  16. python字符串函数使用_Python字符串函数用法
  17. 第三季-第16课-信号量互斥编程
  18. Android P新特性
  19. 过游戏保护系列-巨人游戏保护(1)过PcHunter和Od检测
  20. 基于Labview的串口通信助手,附带免费的exe程序和vi文件(有意见或学习讨论欢迎交流)

热门文章

  1. mac 系统怎么更改 pip 源
  2. 5G-NR非连续接收DRX参数配置详解
  3. 第十六讲:完美主义身心健康 第十七讲:身心健康:运动与冥想 第十八讲:睡眠,触摸和爱情的重要性
  4. java项目——人机猜拳
  5. 告别单调, 给Unity编辑器换一个皮肤吧
  6. 互联网中越老越吃香的行业是?
  7. 怎么找回xmind意外关机的文件_Xmind 导图保存丢失怎么找回?
  8. python 等值线图_如何用Python绘出三值等值线图?
  9. vue常见面试题(附带答案)超实用!!建议收藏!!
  10. 【100%通过率】华为OD机试真题 JS 实现【最接近最大输出功率的设备 /查找充电设备组合】【2023 Q1 | 200分】