【实用小技巧】RSA非对称加解密及XML&PEM格式互换方案
发布日期:2021-05-09 09:34:28 浏览次数:21 分类:博客文章

本文共 10261 字,大约阅读时间需要 34 分钟。

​ 最近因考虑接口安全问题,有实现给WEB API实现统一的参数鉴权功能,以防止请求参数被篡改或重复执行,参数鉴权方法基本与常见的鉴权思路相同,采用(timestamp+sign),而我为了防止timestamp被更改,sign算法(timestamp+相关参数排序、格式化后拼接再MD5)也因为在前端是不安全的,故对timestamp采取使用非对称加解密,以尽可能的保证生成的sign不易被破解或替换;

RSA加解密(即:非对称加解密)

生成公钥、私钥对方法(C#),生成出来后默认都是XML格式:

public static Tuple
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格式方案时,都是复制自: 这篇文章,但其实这篇文章也只是写了私钥XML转PEM格式,并没有说明公钥XML如何转PEM格式,而且只写了支持从文件中获取内容再转换,方案不全,但是给了我(梦在旅途, 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{    ///     /// RSA公钥、私钥对格式(XML与PEM)转换器    /// author:zuowenjun    /// date:2020-12-29    ///     public static class RsaKeysFormatConverter    {        ///         /// XML公钥转成Pem公钥        ///         ///         /// 
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; } /// /// Pem公钥转成XML公钥 /// /// ///
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; } /// /// XML私钥转成PEM私钥 /// /// ///
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; } /// /// Pem私钥转成XML私钥 /// /// ///
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)

当然也可以不用这么费劲自己实现格式转换,可以使用在线网站直接转换: ,另外也有一篇文章实现了类似的功能,但生成的PEM格式并非完整的格式,缺少注释头尾:

上一篇:feignclient各种使用技巧说明
下一篇:关于ASP.NET WEB API(OWIN WEBAPI)的几个编码最佳实践总结

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月28日 07时49分08秒