【实用小技巧】RSA非对称加解密及XML&PEM格式互换方案
最近因考虑接口安全问题,有实现给WEB API实现统一的参数鉴权功能,以防止请求参数被篡改或重复执行,参数鉴权方法基本与常见的鉴权思路相同,采用(timestamp+sign),而我为了防止timestamp被更改,sign算法(timestamp+相关参数排序、格式化后拼接再MD5)也因为在前端是不安全的,故对timestamp采取使用非对称加解密,以尽可能的保证生成的sign不易被破解或替换;
RSA加解密(即:非对称加解密)
生成公钥、私钥对方法(C#),生成出来后默认都是XML格式:
public static Tuple<string, string> GeneratePublicAndPrivateKeyPair()
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
string publicKey = rsa.ToXmlString(false); // 公钥
string privateKey = rsa.ToXmlString(true); // 私钥
return Tuple.Create(publicKey, privateKey);
}
}
使用公钥加密:(支持分段加密,普通单次加密可能会因为内容过长而报错)
public static string RSAEncrypt(string publicKey, string rawInput)
{
if (string.IsNullOrEmpty(rawInput))
{
return string.Empty;
}
if (string.IsNullOrWhiteSpace(publicKey))
{
throw new ArgumentException("Invalid Public Key");
}
using (var rsaProvider = new RSACryptoServiceProvider())
{
var inputBytes = Encoding.UTF8.GetBytes(rawInput);//有含义的字符串转化为字节流
rsaProvider.FromXmlString(publicKey);//载入公钥
int bufferSize = (rsaProvider.KeySize / 8) - 11;//单块最大长度
var buffer = new byte[bufferSize];
using (MemoryStream inputStream = new MemoryStream(inputBytes),
outputStream = new MemoryStream())
{
while (true)
{ //分段加密
int readSize = inputStream.Read(buffer, 0, bufferSize);
if (readSize <= 0)
{
break;
}
var temp = new byte[readSize];
Array.Copy(buffer, 0, temp, 0, readSize);
var encryptedBytes = rsaProvider.Encrypt(temp, false);
outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
}
return Convert.ToBase64String(outputStream.ToArray());//转化为字节流方便传输
}
}
}
使用私钥解密:(支持分段解密,普通单次解密可能会因为密文过长而报错)
public static string RSADecrypt(string privateKey,string encryptedInput)
{
if (string.IsNullOrEmpty(encryptedInput))
{
return string.Empty;
}
if (string.IsNullOrWhiteSpace(privateKey))
{
throw new ArgumentException("Invalid Private Key");
}
using (var rsaProvider = new RSACryptoServiceProvider())
{
var inputBytes = Convert.FromBase64String(encryptedInput);
rsaProvider.FromXmlString(privateKey);
int bufferSize = rsaProvider.KeySize / 8;
var buffer = new byte[bufferSize];
using (MemoryStream inputStream = new MemoryStream(inputBytes),
outputStream = new MemoryStream())
{
while (true)
{
int readSize = inputStream.Read(buffer, 0, bufferSize);
if (readSize <= 0)
{
break;
}
var temp = new byte[readSize];
Array.Copy(buffer, 0, temp, 0, readSize);
var rawBytes = rsaProvider.Decrypt(temp, false);
outputStream.Write(rawBytes, 0, rawBytes.Length);
}
return Encoding.UTF8.GetString(outputStream.ToArray());
}
}
}
如果都是C#项目可能如上两个方法就可以了,但如果需要与WEB前端、JAVA等其它编程语言协同交互处理时(比如:WEB前端用公钥加密,后端C#私钥解密),则可能因为公钥与私钥的格式不相同而导致无法正常的进行对接【前端、JAVA 等语言使用的是PEM格式的,而C#使用的是XML格式】,网上查XML转PEM格式方案时,都是复制自:https://www.cnblogs.com/micenote/p/7862989.html 这篇文章,但其实这篇文章也只是写了私钥XML转PEM格式,并没有说明公钥XML如何转PEM格式,而且只写了支持从文件中获取内容再转换,方案不全,但是给了我(梦在旅途,http://www.zuowenjun.cn or zuowj.cnblogs.com.cn)思路,我经过各种验证,最终实现了比较友好的PEM与XML格式的相互转换方式,且经过单元测试验证通过,在此分享给大家。
如下是完整的XML与PEM格式转换器类代码;(注意需引入BouncyCastle nuget包)
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Zuowj.Common
{
/// <summary>
/// RSA公钥、私钥对格式(XML与PEM)转换器
/// author:zuowenjun
/// date:2020-12-29
/// </summary>
public static class RsaKeysFormatConverter
{
/// <summary>
/// XML公钥转成Pem公钥
/// </summary>
/// <param name="xmlPublicKey"></param>
/// <returns></returns>
public static string XmlPublicKeyToPem(string xmlPublicKey)
{
RSAParameters rsaParam;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(xmlPublicKey);
rsaParam = rsa.ExportParameters(false);
}
RsaKeyParameters param = new RsaKeyParameters(false, new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent));
string pemPublicKeyStr = null;
using (var ms = new MemoryStream())
{
using (var sw = new StreamWriter(ms))
{
var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw);
pemWriter.WriteObject(param);
sw.Flush();
byte[] buffer = new byte[ms.Length];
ms.Position = 0;
ms.Read(buffer, 0, (int)ms.Length);
pemPublicKeyStr = Encoding.UTF8.GetString(buffer);
}
}
return pemPublicKeyStr;
}
/// <summary>
/// Pem公钥转成XML公钥
/// </summary>
/// <param name="pemPublicKeyStr"></param>
/// <returns></returns>
public static string PemPublicKeyToXml(string pemPublicKeyStr)
{
RsaKeyParameters pemPublicKey;
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPublicKeyStr)))
{
using (var sr = new StreamReader(ms))
{
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr);
pemPublicKey = (RsaKeyParameters)pemReader.ReadObject();
}
}
var p = new RSAParameters
{
Modulus = pemPublicKey.Modulus.ToByteArrayUnsigned(),
Exponent = pemPublicKey.Exponent.ToByteArrayUnsigned()
};
string xmlPublicKeyStr;
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(p);
xmlPublicKeyStr = rsa.ToXmlString(false);
}
return xmlPublicKeyStr;
}
/// <summary>
/// XML私钥转成PEM私钥
/// </summary>
/// <param name="xmlPrivateKey"></param>
/// <returns></returns>
public static string XmlPrivateKeyToPem(string xmlPrivateKey)
{
RSAParameters rsaParam;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(xmlPrivateKey);
rsaParam = rsa.ExportParameters(true);
}
var param = new RsaPrivateCrtKeyParameters(
new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent), new BigInteger(1, rsaParam.D),
new BigInteger(1, rsaParam.P), new BigInteger(1, rsaParam.Q), new BigInteger(1, rsaParam.DP), new BigInteger(1, rsaParam.DQ),
new BigInteger(1, rsaParam.InverseQ));
string pemPrivateKeyStr = null;
using (var ms = new MemoryStream())
{
using (var sw = new StreamWriter(ms))
{
var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw);
pemWriter.WriteObject(param);
sw.Flush();
byte[] buffer = new byte[ms.Length];
ms.Position = 0;
ms.Read(buffer, 0, (int)ms.Length);
pemPrivateKeyStr = Encoding.UTF8.GetString(buffer);
}
}
return pemPrivateKeyStr;
}
/// <summary>
/// Pem私钥转成XML私钥
/// </summary>
/// <param name="pemPrivateKeyStr"></param>
/// <returns></returns>
public static string PemPrivateKeyToXml(string pemPrivateKeyStr)
{
RsaPrivateCrtKeyParameters pemPrivateKey;
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPrivateKeyStr)))
{
using (var sr = new StreamReader(ms))
{
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr);
var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
pemPrivateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;
}
}
var p = new RSAParameters
{
Modulus = pemPrivateKey.Modulus.ToByteArrayUnsigned(),
Exponent = pemPrivateKey.PublicExponent.ToByteArrayUnsigned(),
D = pemPrivateKey.Exponent.ToByteArrayUnsigned(),
P = pemPrivateKey.P.ToByteArrayUnsigned(),
Q = pemPrivateKey.Q.ToByteArrayUnsigned(),
DP = pemPrivateKey.DP.ToByteArrayUnsigned(),
DQ = pemPrivateKey.DQ.ToByteArrayUnsigned(),
InverseQ = pemPrivateKey.QInv.ToByteArrayUnsigned(),
};
string xmlPrivateKeyStr;
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(p);
xmlPrivateKeyStr = rsa.ToXmlString(true);
}
return xmlPrivateKeyStr;
}
}
}
如下是单元测试代码:
//公钥(XML、PEM格式互)测试
string srcPublicKey = “具体的XML Public Key”;
string pemPublicKeyStr= RsaKeysFormatConverter.XmlPublicKeyToPem(publicKey);
string xmlPublicKeyStr= RsaKeysFormatConverter.PemPublicKeyToXml(pemPublicKeyStr);
Assert.AreEqual(srcPublicKey, xmlPublicKeyStr);
//私钥(XML、PEM格式互)测试
string srcPrivateKey = “具体的XML Private Key”;
string pemPrivateKeyStr = RsaKeysFormatConverter.XmlPrivateKeyToPem(srcPrivateKey);
string xmlPrivateKeyStr = RsaKeysFormatConverter.PemPrivateKeyToXml(pemPrivateKeyStr);
Assert.AreEqual(privateKey,xmlPrivateKeyStr)
当然也可以不用这么费劲自己实现格式转换,可以使用在线网站直接转换:https://the-x.cn/certificate/XmlToPem.aspx ,另外也有一篇文章实现了类似的功能,但生成的PEM格式并非完整的格式,缺少注释头尾:https://www.cnblogs.com/datous/p/RSAKeyConvert.html
【实用小技巧】RSA非对称加解密及XML&PEM格式互换方案的更多相关文章
- php rsa 非对称加解密类
<?php header("Content-Type: text/html;charset=utf-8"); /* 生成公钥.私钥对,私钥加密的内容能通过公钥解密(反过来亦可 ...
- ios php RSA 非对称加密解密 der 和pem生成
ios 使用public_key.der加密 php 使用 private_key.pem解密 openssl req -x509 -out public_key.der -outform der - ...
- [Python3] RSA的加解密和签名/验签实现 -- 使用pycrytodome
Crypto 包介绍: pycrypto,pycrytodome 和 crypto 是一个东西,crypto 在 python 上面的名字是 pycrypto 它是一个第三方库,但是已经停止更新,所以 ...
- VC6.0实用小技巧
VC6.0的若干实用小技巧 .检测程序中的括号是否匹配 把光标移动到需要检测的括号(如大括号{}.方括号[].圆括号()和尖括号<>)前面,键入快捷键 “Ctrl+]”.如果括号匹配正确, ...
- 非对称加解密 Asymmetric encryption 对称加密和非对称加密的区别
考虑这样一个问题:一切的装备文件都存储在 Git 长途库房,RAR密码破解装备文件中的一些信息又是比较灵敏的.所以,我们需求对这些灵敏信息进行加密处理.首要的加密方法分为两种:一种是同享密钥加 密(对 ...
- PHP 常用函数库和一些实用小技巧
PHP 常用函数库和一些实用小技巧 作者: 字体:[增加 减小] 类型:转载 包括文件读取函式,文件写入函式,静态页面生成函式,目录删除函式等 文件读取函式 //文件读取函式 function ...
- Vim实用小技巧
Vim实用小技巧 一些网络上质量较高的Vim资料 从我07年接触Vim以来,已经过去了8个年头,期间看过很多的Vim文章,我自己觉得非常不错,而且创作时间也比较近的文章有如下这些. Vim入门 目前为 ...
- svn checkout 实用小技巧
svn checkout 实用小技巧 by:授客 QQ:1033553122 问题描述: 用svn小乌龟软件,进行update,commit之前,先要把svn工作目录checkout到本地,那么问 ...
- 实用小技巧(一):UIScrollView中上下左右滚动方向的判断
https://www.jianshu.com/p/93e8459b6dae 2017.06.01 01:13* 字数 674 阅读 1201评论 0喜欢 1 2017.06.01 01:13* 字数 ...
- SM2的非对称加解密java工具类
maven依赖 <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov- ...
随机推荐
- go语言-Go环境搭建
go语言-Go环境搭建 下载 https://golang.org/dl/ 切换root权限 su root 进入用户列表 cd /usr/local/ 解压缩 tar -zxvf go1.13.li ...
- java获取年月日、时间与区间、Sql获取年月日区间
SQL 获取时.日.周.月日期 因工作上常用到统计分析,需要用到具体的时间,故写于此 24小时: SELECT 0 AS hour UNION ALL SELECT 1 AS hour UNION A ...
- 杭州站|阿里云 Serverless 技术实践营(Serverless + 大数据)开启报名!
活动简介 "Serverless 技术实战与创新沙龙 " 是一场以 Serverless 为主题的开发者活动,通过一个下午的时间增进对 Serverless 技术的理解,快速上手, ...
- C#设计模式14——模板方法的写法
模板方法(Template Method)是一种设计模式,它定义了一个操作中的算法的骨架,将某些步骤推迟到子类中实现,从而使得子类可以在不改变算法骨架的情况下重新定义算法的某些步骤. 作用: 使用模板 ...
- Servlet系列:(HttpServletRequest、HttpServletResponse、ServletContext、ServletConfig)详解
HttpServletRequest HttpServletRequest 对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service() ...
- Solon v2.6.5 发布(助力信创)
Solon 是什么框架? Java "生态级"应用开发框架.从零开始构建,有自己的标准规范与开放生态(历时六年,具备全球第二级别的生态规模). 相对于 Spring,有什么特点? ...
- npm, yarn和pnpm清理缓存
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 【TouchGFX】使用v4.18.1版本创建预制电路板工程的正确方法
选择要使用的电路板 实现自己的程序 Designer运行仿真没问题并生成代码 我习惯使用IAR工具,发现直接编译有错误 上述错误是因为Designer默认生成的工具链是CubeIDE,所以需要使用Cu ...
- Android Studio 的 Gradle 面板没有 Task
问题描述:Android Studio Gradle 窗口没有显示 task 列表的问题,如下图所示: 网上找了好久都没有找到原因,最后自己摸索,找了解决方法. 解决方法:依次点击:File -> ...
- [转帖]harbor-db restarting问题
现象: 在安装harbor后,启动时发现harbor-db 一直是restarting,/harbor-jobservice,/harbor-core 这两是starting 状态,如下图 解决: 1 ...