zoukankan      html  css  js  c++  java
  • 【实用小技巧】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

  • 相关阅读:
    #QCon#北京2011大会“更有效地做测试”专题Slides资料
    长草了,冒个泡
    很好的一个书单
    团购——以价格换体验
    rpm deb命令集合[转]
    一个自动copy文件到指定目录的小程序
    ubuntulinux下的精品软件大汇总
    解决星际译王不朗读单词问题
    数据结构实验三:二叉树及其应用
    数据结构实验六:内部排序技术
  • 原文地址:https://www.cnblogs.com/zuowj/p/14207740.html
Copyright © 2011-2022 走看看