1. 程式人生 > 程式設計 >c# RSA非對稱加解密及XML&PEM格式互換方案

c# 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,bufferSize);
            if (readSize <= 0)
            {
              break;
            }

            var temp = new byte[readSize];
            Array.Copy(buffer,temp,readSize);
            var encryptedBytes = rsaProvider.Encrypt(temp,false);
            outputStream.Write(encryptedBytes,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,readSize);
            var rawBytes = rsaProvider.Decrypt(temp,false);
            outputStream.Write(rawBytes,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),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,(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.Exponent),rsaParam.D),rsaParam.P),rsaParam.Q),rsaParam.DP),rsaParam.DQ),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,(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非對稱加解密的資料請關注我們其它相關文章!