c# RSA非对称加解密及XML&PEM格式互换方案

 更新时间:2020年12月31日 09:14:27   作者:梦在旅途  
这篇文章主要介绍了c# RSA非对称加解密及XML&PEM格式互换方案,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下

最近因考虑接口安全问题,有实现给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格式,而且只写了支持从文件中获取内容再转换,方案不全,但是给了我思路,我经过各种验证,最终实现了比较友好的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

以上就是c# RSA非对称加解密及XML&PEM格式互换方案的详细内容,更多关于c# RSA非对称加解密的资料请关注脚本之家其它相关文章!

相关文章

  • C# wpf 无边框窗口添加阴影效果的实现

    C# wpf 无边框窗口添加阴影效果的实现

    在本篇内容中小编给大家整理了一篇关于C# wpf 无边框窗口添加阴影效果的具体方法内容,有兴趣的朋友们可以学习参考下
    2022-11-11
  • C# list<T>去重的实现

    C# list<T>去重的实现

    List集合在开发过程中很常见,经常我们要对该集合进行一系列操作,本文主要介绍了C# list<T>去重的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • WinForm中Application.Idle方法详解

    WinForm中Application.Idle方法详解

    本文详细讲解了WinForm中的Application.Idle方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • C#使用NPOI实现Excel和DataTable的互转

    C#使用NPOI实现Excel和DataTable的互转

    这篇文章主要为大家详细介绍了C#使用NPOI实现Excel和DataTable的互转,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • C#获取Excel第一列的实例方法

    C#获取Excel第一列的实例方法

    在本篇文章里小编给各位分享了关于C# 怎么获取Excel第一列的实例方法和代码,需要的朋友们可以学习下。
    2019-08-08
  • Unity实现图片轮播组件

    Unity实现图片轮播组件

    这篇文章主要为大家详细介绍了Unity实现图片轮播组件的相关方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • C#工程建立后修改工程文件名与命名空间操作

    C#工程建立后修改工程文件名与命名空间操作

    这篇文章主要介绍了C#工程建立后修改工程文件名与命名空间操作,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • C#中Invoke和BeginInvoke区别小结

    C#中Invoke和BeginInvoke区别小结

    有时候,我们不得不跨线程调用主界面的控件来进行操作,所以为了方便的解决问题,.net为我们提供了Invoke 与beginInvoke,那么Invoke和BeginInvoke区别在哪,本文就来详细的介绍一下
    2023-08-08
  • 如何:对Windows 窗体控件进行线程安全调用

    如何:对Windows 窗体控件进行线程安全调用

    使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。
    2007-03-03
  • c#中WebService的介绍及调用方式小结

    c#中WebService的介绍及调用方式小结

    这篇文章主要给大家介绍了关于c#中的WebService及其调用方式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-11-11

最新评论