c# RSA非对称加解密及XML&PEM格式互换方案
最近因考虑接口安全问题,有实现给WEBAPI实现统一的参数鉴权功能,以防止请求参数被篡改或重复执行,参数鉴权方法基本与常见的鉴权思路相同,采用(timestamp+sign),而我为了防止timestamp被更改,sign算法(timestamp+相关参数排序、格式化后拼接再MD5)也因为在前端是不安全的,故对timestamp采取使用非对称加解密,以尽可能的保证生成的sign不易被破解或替换;
RSA加解密(即:非对称加解密)
生成公钥、私钥对方法(C#),生成出来后默认都是XML格式:
publicstaticTupleGeneratePublicAndPrivateKeyPair() { using(RSACryptoServiceProviderrsa=newRSACryptoServiceProvider()) { stringpublicKey=rsa.ToXmlString(false);//公钥 stringprivateKey=rsa.ToXmlString(true);//私钥 returnTuple.Create(publicKey,privateKey); } }
使用公钥加密:(支持分段加密,普通单次加密可能会因为内容过长而报错)
publicstaticstringRSAEncrypt(stringpublicKey,stringrawInput) { if(string.IsNullOrEmpty(rawInput)) { returnstring.Empty; } if(string.IsNullOrWhiteSpace(publicKey)) { thrownewArgumentException("InvalidPublicKey"); } using(varrsaProvider=newRSACryptoServiceProvider()) { varinputBytes=Encoding.UTF8.GetBytes(rawInput);//有含义的字符串转化为字节流 rsaProvider.FromXmlString(publicKey);//载入公钥 intbufferSize=(rsaProvider.KeySize/8)-11;//单块最大长度 varbuffer=newbyte[bufferSize]; using(MemoryStreaminputStream=newMemoryStream(inputBytes), outputStream=newMemoryStream()) { while(true) {//分段加密 intreadSize=inputStream.Read(buffer,0,bufferSize); if(readSize<=0) { break; } vartemp=newbyte[readSize]; Array.Copy(buffer,0,temp,0,readSize); varencryptedBytes=rsaProvider.Encrypt(temp,false); outputStream.Write(encryptedBytes,0,encryptedBytes.Length); } returnConvert.ToBase64String(outputStream.ToArray());//转化为字节流方便传输 } } }
使用私钥解密:(支持分段解密,普通单次解密可能会因为密文过长而报错)
publicstaticstringRSADecrypt(stringprivateKey,stringencryptedInput) { if(string.IsNullOrEmpty(encryptedInput)) { returnstring.Empty; } if(string.IsNullOrWhiteSpace(privateKey)) { thrownewArgumentException("InvalidPrivateKey"); } using(varrsaProvider=newRSACryptoServiceProvider()) { varinputBytes=Convert.FromBase64String(encryptedInput); rsaProvider.FromXmlString(privateKey); intbufferSize=rsaProvider.KeySize/8; varbuffer=newbyte[bufferSize]; using(MemoryStreaminputStream=newMemoryStream(inputBytes), outputStream=newMemoryStream()) { while(true) { intreadSize=inputStream.Read(buffer,0,bufferSize); if(readSize<=0) { break; } vartemp=newbyte[readSize]; Array.Copy(buffer,0,temp,0,readSize); varrawBytes=rsaProvider.Decrypt(temp,false); outputStream.Write(rawBytes,0,rawBytes.Length); } returnEncoding.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格式,而且只写了支持从文件中获取内容再转换,方案不全,但是给了我思路,我经过各种验证,最终实现了比较友好的PEM与XML格式的相互转换方式,且经过单元测试验证通过,在此分享给大家。
如下是完整的XML与PEM格式转换器类代码;(注意需引入BouncyCastlenuget包)
usingOrg.BouncyCastle.Crypto; usingOrg.BouncyCastle.Crypto.Parameters; usingOrg.BouncyCastle.Math; usingSystem; usingSystem.Collections.Generic; usingSystem.IO; usingSystem.Linq; usingSystem.Security.Cryptography; usingSystem.Text; usingSystem.Threading.Tasks; namespaceZuowj.Common { //////RSA公钥、私钥对格式(XML与PEM)转换器 ///author:zuowenjun ///date:2020-12-29 /// publicstaticclassRsaKeysFormatConverter { //////XML公钥转成Pem公钥 /// ////// publicstaticstringXmlPublicKeyToPem(stringxmlPublicKey) { RSAParametersrsaParam; using(RSACryptoServiceProviderrsa=newRSACryptoServiceProvider()) { rsa.FromXmlString(xmlPublicKey); rsaParam=rsa.ExportParameters(false); } RsaKeyParametersparam=newRsaKeyParameters(false,newBigInteger(1,rsaParam.Modulus),newBigInteger(1,rsaParam.Exponent)); stringpemPublicKeyStr=null; using(varms=newMemoryStream()) { using(varsw=newStreamWriter(ms)) { varpemWriter=newOrg.BouncyCastle.OpenSsl.PemWriter(sw); pemWriter.WriteObject(param); sw.Flush(); byte[]buffer=newbyte[ms.Length]; ms.Position=0; ms.Read(buffer,0,(int)ms.Length); pemPublicKeyStr=Encoding.UTF8.GetString(buffer); } } returnpemPublicKeyStr; } /// ///Pem公钥转成XML公钥 /// ////// publicstaticstringPemPublicKeyToXml(stringpemPublicKeyStr) { RsaKeyParameterspemPublicKey; using(varms=newMemoryStream(Encoding.UTF8.GetBytes(pemPublicKeyStr))) { using(varsr=newStreamReader(ms)) { varpemReader=newOrg.BouncyCastle.OpenSsl.PemReader(sr); pemPublicKey=(RsaKeyParameters)pemReader.ReadObject(); } } varp=newRSAParameters { Modulus=pemPublicKey.Modulus.ToByteArrayUnsigned(), Exponent=pemPublicKey.Exponent.ToByteArrayUnsigned() }; stringxmlPublicKeyStr; using(varrsa=newRSACryptoServiceProvider()) { rsa.ImportParameters(p); xmlPublicKeyStr=rsa.ToXmlString(false); } returnxmlPublicKeyStr; } /// ///XML私钥转成PEM私钥 /// ////// publicstaticstringXmlPrivateKeyToPem(stringxmlPrivateKey) { RSAParametersrsaParam; using(RSACryptoServiceProviderrsa=newRSACryptoServiceProvider()) { rsa.FromXmlString(xmlPrivateKey); rsaParam=rsa.ExportParameters(true); } varparam=newRsaPrivateCrtKeyParameters( newBigInteger(1,rsaParam.Modulus),newBigInteger(1,rsaParam.Exponent),newBigInteger(1,rsaParam.D), newBigInteger(1,rsaParam.P),newBigInteger(1,rsaParam.Q),newBigInteger(1,rsaParam.DP),newBigInteger(1,rsaParam.DQ), newBigInteger(1,rsaParam.InverseQ)); stringpemPrivateKeyStr=null; using(varms=newMemoryStream()) { using(varsw=newStreamWriter(ms)) { varpemWriter=newOrg.BouncyCastle.OpenSsl.PemWriter(sw); pemWriter.WriteObject(param); sw.Flush(); byte[]buffer=newbyte[ms.Length]; ms.Position=0; ms.Read(buffer,0,(int)ms.Length); pemPrivateKeyStr=Encoding.UTF8.GetString(buffer); } } returnpemPrivateKeyStr; } /// ///Pem私钥转成XML私钥 /// ////// publicstaticstringPemPrivateKeyToXml(stringpemPrivateKeyStr) { RsaPrivateCrtKeyParameterspemPrivateKey; using(varms=newMemoryStream(Encoding.UTF8.GetBytes(pemPrivateKeyStr))) { using(varsr=newStreamReader(ms)) { varpemReader=newOrg.BouncyCastle.OpenSsl.PemReader(sr); varkeyPair=(AsymmetricCipherKeyPair)pemReader.ReadObject(); pemPrivateKey=(RsaPrivateCrtKeyParameters)keyPair.Private; } } varp=newRSAParameters { 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(), }; stringxmlPrivateKeyStr; using(varrsa=newRSACryptoServiceProvider()) { rsa.ImportParameters(p); xmlPrivateKeyStr=rsa.ToXmlString(true); } returnxmlPrivateKeyStr; } } }
如下是单元测试代码:
//公钥(XML、PEM格式互)测试 stringsrcPublicKey=“具体的XMLPublicKey”; stringpemPublicKeyStr=RsaKeysFormatConverter.XmlPublicKeyToPem(publicKey); stringxmlPublicKeyStr=RsaKeysFormatConverter.PemPublicKeyToXml(pemPublicKeyStr); Assert.AreEqual(srcPublicKey,xmlPublicKeyStr);
//私钥(XML、PEM格式互)测试 stringsrcPrivateKey=“具体的XMLPrivateKey”; stringpemPrivateKeyStr=RsaKeysFormatConverter.XmlPrivateKeyToPem(srcPrivateKey); stringxmlPrivateKeyStr=RsaKeysFormatConverter.PemPrivateKeyToXml(pemPrivateKeyStr); Assert.AreEqual(privateKey,xmlPrivateKeyStr)
当然也可以不用这么费劲自己实现格式转换,可以使用在线网站直接转换:https://the-x.cn/certificate/XmlToPem.aspx,另外也有一篇文章实现了类似的功能,但生成的PEM格式并非完整的格式,缺少注释头尾:https://www.cnblogs.com/datous/p/RSAKeyConvert.html
以上就是c#RSA非对称加解密及XML&PEM格式互换方案的详细内容,更多关于c#RSA非对称加解密的资料请关注毛票票其它相关文章!