詳解c#與js的rsa加密互通

ASN.1

 抽象語法表示(標記)ASN.1(Abstract Syntax Notation One )一種數據定義語言,描述瞭對數據進行表示、編碼、傳輸和解碼的數據格式。網絡管理系統中的管理信息庫(MIB)、應用程序的數據結構、協議數據單元(PDU)都是用ASN.1定義的。

可以理解為ASN.1是對密鑰結構定義的一種規范

密鑰結構類型

PKCS#1

RSAPublicKey ::= SEQUENCE {
  modulus      INTEGER, -- n
  publicExponent  INTEGER  -- e
}

RSAPrivateKey ::= SEQUENCE {
 version      Version,
 modulus      INTEGER, -- n
 publicExponent  INTEGER, -- e
 privateExponent  INTEGER, -- d
 prime1      INTEGER, -- p
 prime2      INTEGER, -- q
 exponent1     INTEGER, -- d mod (p-1)
 exponent2     INTEGER, -- d mod (q-1)
 coefficient    INTEGER, -- (inverse of q) mod p
 otherPrimeInfos  OtherPrimeInfos OPTIONAL
}

PKCS#8

PublicKeyInfo ::= SEQUENCE {
 algorithm    AlgorithmIdentifier,
 PublicKey    BIT STRING ; 其中的BIT STRING是某個算法自己指定的二進制格式
               ; RSA算法的話,就是上面的RSAPublicKey
}

AlgorithmIdentifier ::= SEQUENCE {
 algorithm    OBJECT IDENTIFIER,
 parameters   ANY DEFINED BY algorithm OPTIONAL
}

PrivateKeyInfo ::= SEQUENCE {
 version     Version,
 algorithm    AlgorithmIdentifier,
 PrivateKey   BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
 algorithm    OBJECT IDENTIFIER,
 parameters   ANY DEFINED BY algorithm OPTIONAL
}

密鑰編碼類型

der格式

二進制格式

pem格式

把der格式的數據用base64編碼後,然後再在頭尾加上一段“—–”開始的標記

證書類型

X.509證書

X.509隻包含公鑰,沒有私鑰,這種證書一般公開發佈,可用於放在客服端使用,用於加密、驗簽

PKCS#12證書

因為X.509證書隻包含公鑰,但有些時候我們需要把私鑰和公鑰合並成一個證書,放在服務端使用,用於解密、簽名。

PKCS#12就定義瞭這樣一種證書,它既包含瞭公鑰有包含瞭私鑰。典型的入pfx、p12證書就是PKCS#12證書。

PKCS#7證書

當你收到一個網站的證書後,你需要驗證其真實性。因為一個X.509證書包含瞭公鑰、持有人信息、簽名。為瞭驗證其真實性,你需要簽證其簽名,而驗證簽名則需要簽發的CA機構的公鑰證書。同樣原理,當你拿到CA機構的公鑰證書後,你也需要驗證該CA機構的真實性,而驗證該CA機構的證書,你需要該CA上級機構的CA公鑰證書…以此類推,你需要一直驗證到根證書為止。所以為瞭驗證一個網站證書的真實性,你需要的不僅一張證書,而是一個證書鏈。而PKCS#7就定義瞭這樣一個證書鏈的類型結構。典型如p7b後綴名的證書就是這樣的格式。

證書後綴

.cer/.crt:存放公鑰,沒有私鑰,就是一個X.509證書,二進制形式存放

.pfx/.p12:存放公鑰和私鑰,通常包含保護密碼,二進制方式

證書與密鑰關系

數字證書和私鑰是匹配的關系。就好比鑰匙牌和鑰匙的關系。在數字證書簽發的時候,數字證書簽發系統(CA系統),在生成數字證書的同時,還會隨機生成一對密鑰,一個私鑰,一個公鑰。數字證書標示用戶身份, 相匹配的私鑰和公鑰,則是用來保障用戶身份的可認證性。就好比咱們拿著一串鑰匙,每個鑰匙上都標明有時某某房間的鑰匙,但是否是真的,還需要看能不能打開相應的房門。

密鑰生成

/// <summary>
    /// 取得私鑰和公鑰 XML 格式,返回數組第一個是私鑰,第二個是公鑰.
    /// </summary>
    /// <param name="size">密鑰長度,默認1024,可以為2048</param>
    /// <returns></returns>
    public static string[] CreateXmlKey(int size = 1024)
    {
      //密鑰格式要生成pkcs#1格式的 而不是pkcs#8格式的
      RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
      string privateKey = sp.ToXmlString(true);//private key
      string publicKey = sp.ToXmlString(false);//public key
      return new string[] { privateKey, publicKey };
    }

    /// <summary>
    /// 取得私鑰和公鑰 CspBlob 格式,返回數組第一個是私鑰,第二個是公鑰.
    /// </summary>
    /// <param name="size"></param>
    /// <returns></returns>
    public static string[] CreateCspBlobKey(int size = 1024)
    {
      //密鑰格式要生成pkcs#1格式的 而不是pkcs#8格式的
      RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
      string privateKey = System.Convert.ToBase64String(sp.ExportCspBlob(true));//private key
      string publicKey = System.Convert.ToBase64String(sp.ExportCspBlob(false));//public key 

      return new string[] { privateKey, publicKey };
    }
    /// <summary>
    /// 導出PEM PKCS#1格式密鑰對,返回數組第一個是私鑰,第二個是公鑰.
    /// </summary>
    public static string[] CreateKey_PEM_PKCS1(int size = 1024)
    {
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
      string privateKey = RSA_PEM.ToPEM(rsa, false, false);
      string publicKey = RSA_PEM.ToPEM(rsa, true, false);
      return new string[] { privateKey, publicKey };
    }

    /// <summary>
    /// 導出PEM PKCS#8格式密鑰對,返回數組第一個是私鑰,第二個是公鑰.
    /// </summary>
    public static string[] CreateKey_PEM_PKCS8(int size = 1024, bool convertToPublic = false)
    {
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
      string privateKey = RSA_PEM.ToPEM(rsa, false, true);
      string publicKey = RSA_PEM.ToPEM(rsa, true, true);
      return new string[] { privateKey, publicKey };

    }

後端加/解密方法使用

/// <summary>
    /// RSA加密
    /// </summary>
    /// <param name="Data">原文</param>
    /// <param name="PublicKeyString">公鑰</param>
    /// <param name="KeyType">密鑰類型XML/PEM</param>
    /// <returns></returns>
    public static string RSAEncrypt(string Data,string PublicKeyString,string KeyType)
    {
      byte[] data = Encoding.GetEncoding("UTF-8").GetBytes(Data);
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
      switch (KeyType)
      {
        case "XML":
          rsa.FromXmlString(PublicKeyString);
          break;
        case "PEM":
          rsa = RSA_PEM.FromPEM(PublicKeyString);
          break;
        default:
          throw new Exception("不支持的密鑰類型");
      }
      //加密塊最大長度限制,如果加密數據的長度超過 秘鑰長度/8-11,會引發長度不正確的異常,所以進行數據的分塊加密
      int MaxBlockSize = rsa.KeySize / 8 - 11;
      //正常長度
      if (data.Length <= MaxBlockSize)
      {
        byte[] hashvalueEcy = rsa.Encrypt(data, false); //加密
        return System.Convert.ToBase64String(hashvalueEcy);
      }
      //長度超過正常值
      else
      {
        using (MemoryStream PlaiStream = new MemoryStream(data))
        using (MemoryStream CrypStream = new MemoryStream())
        {
          Byte[] Buffer = new Byte[MaxBlockSize];
          int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
          while (BlockSize > 0)
          {
            Byte[] ToEncrypt = new Byte[BlockSize];
            Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);

            Byte[] Cryptograph = rsa.Encrypt(ToEncrypt, false);
            CrypStream.Write(Cryptograph, 0, Cryptograph.Length);
            BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
          }
          return System.Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
        }
      }
    }

    /// <summary>
    /// RSA解密
    /// </summary>
    /// <param name="Data">密文</param>
    /// <param name="PrivateKeyString">私鑰</param>
    /// <param name="KeyType">密鑰類型XML/PEM</param>
    /// <returns></returns>
    public static string RSADecrypt(string Data,string PrivateKeyString, string KeyType)
    {
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
      switch (KeyType)
      {
        case "XML":
          rsa.FromXmlString(PrivateKeyString);
          break;
        case "PEM":
          rsa = RSA_PEM.FromPEM(PrivateKeyString);
          break;
        default:
          throw new Exception("不支持的密鑰類型");
      }
      int MaxBlockSize = rsa.KeySize / 8;  //解密塊最大長度限制
      //正常解密
      if (Data.Length <= MaxBlockSize)
      {
        byte[] hashvalueDcy = rsa.Decrypt(System.Convert.FromBase64String(Data), false);//解密
        return Encoding.GetEncoding("UTF-8").GetString(hashvalueDcy);
      }
      //分段解密
      else
      {
        using (MemoryStream CrypStream = new MemoryStream(System.Convert.FromBase64String(Data)))
        using (MemoryStream PlaiStream = new MemoryStream())
        {
          Byte[] Buffer = new Byte[MaxBlockSize];
          int BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);

          while (BlockSize > 0)
          {
            Byte[] ToDecrypt = new Byte[BlockSize];
            Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize);

            Byte[] Plaintext = rsa.Decrypt(ToDecrypt, false);
            PlaiStream.Write(Plaintext, 0, Plaintext.Length);
            BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
          }
          string output = Encoding.GetEncoding("UTF-8").GetString(PlaiStream.ToArray());
          return output;
        }
      }
    }

前端加密方法

註:jsencrypt默認PKCS#1結構,生成密鑰時需要註意

<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
 var encryptor = new JSEncrypt() // 創建加密對象實例
 //之前ssl生成的公鑰,復制的時候要小心不要有空格
 var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS'+
 'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----'
 encryptor.setPublicKey(pubKey)//設置公鑰
 var rsaPassWord = encryptor.encrypt('要加密的內容') // 對內容進行加密

c#pem格式轉換

註:c#的RSACryptoServiceProvider默認隻支持xml格式的密鑰解析

public class RSA_Unit
  {
    static public string Base64EncodeBytes(byte[] byts)
    {
      return System.Convert.ToBase64String(byts);
    }
    static public byte[] Base64DecodeBytes(string str)
    {
      try
      {
        return System.Convert.FromBase64String(str);
      }
      catch
      {
        return null;
      }
    }
    /// <summary>
    /// 把字符串按每行多少個字斷行
    /// </summary>
    static public string TextBreak(string text, int line)
    {
      var idx = 0;
      var len = text.Length;
      var str = new StringBuilder();
      while (idx < len)
      {
        if (idx > 0)
        {
          str.Append('\n');
        }
        if (idx + line >= len)
        {
          str.Append(text.Substring(idx));
        }
        else
        {
          str.Append(text.Substring(idx, line));
        }
        idx += line;
      }
      return str.ToString();
    }

  }
  static public class Extensions
  {
    /// <summary>
    /// 從數組start開始到指定長度復制一份
    /// </summary>
    static public T[] sub<T>(this T[] arr, int start, int count)
    {
      T[] val = new T[count];
      for (var i = 0; i < count; i++)
      {
        val[i] = arr[start + i];
      }
      return val;
    }
    static public void writeAll(this Stream stream, byte[] byts)
    {
      stream.Write(byts, 0, byts.Length);
    }
  }
點擊並拖拽以移動
 public class RSA_PEM
  {
    public static RSACryptoServiceProvider FromPEM(string pem)
    {
      var rsaParams = new CspParameters();
      rsaParams.Flags = CspProviderFlags.UseMachineKeyStore;
      var rsa = new RSACryptoServiceProvider(rsaParams);

      var param = new RSAParameters();

      var base64 = _PEMCode.Replace(pem, "");
      var data = RSA_Unit.Base64DecodeBytes(base64);
      if (data == null)
      {
        throw new Exception("PEM內容無效");
      }
      var idx = 0;

      //讀取長度
      Func<byte, int> readLen = (first) =>
      {
        if (data[idx] == first)
        {
          idx++;
          if (data[idx] == 0x81)
          {
            idx++;
            return data[idx++];
          }
          else if (data[idx] == 0x82)
          {
            idx++;
            return (((int)data[idx++]) << 8) + data[idx++];
          }
          else if (data[idx] < 0x80)
          {
            return data[idx++];
          }
        }
        throw new Exception("PEM未能提取到數據");
      };
      //讀取塊數據
      Func<byte[]> readBlock = () =>
      {
        var len = readLen(0x02);
        if (data[idx] == 0x00)
        {
          idx++;
          len--;
        }
        var val = data.sub(idx, len);
        idx += len;
        return val;
      };
      //比較data從idx位置開始是否是byts內容
      Func<byte[], bool> eq = (byts) =>
      {
        for (var i = 0; i < byts.Length; i++, idx++)
        {
          if (idx >= data.Length)
          {
            return false;
          }
          if (byts[i] != data[idx])
          {
            return false;
          }
        }
        return true;
      };




      if (pem.Contains("PUBLIC KEY"))
      {
        /****使用公鑰****/
        //讀取數據總長度
        readLen(0x30);
        if (!eq(_SeqOID))
        {
          throw new Exception("PEM未知格式");
        }
        //讀取1長度
        readLen(0x03);
        idx++;//跳過0x00
           //讀取2長度
        readLen(0x30);

        //Modulus
        param.Modulus = readBlock();

        //Exponent
        param.Exponent = readBlock();
      }
      else if (pem.Contains("PRIVATE KEY"))
      {
        /****使用私鑰****/
        //讀取數據總長度
        readLen(0x30);

        //讀取版本號
        if (!eq(_Ver))
        {
          throw new Exception("PEM未知版本");
        }

        //檢測PKCS8
        var idx2 = idx;
        if (eq(_SeqOID))
        {
          //讀取1長度
          readLen(0x04);
          //讀取2長度
          readLen(0x30);

          //讀取版本號
          if (!eq(_Ver))
          {
            throw new Exception("PEM版本無效");
          }
        }
        else
        {
          idx = idx2;
        }

        //讀取數據
        param.Modulus = readBlock();
        param.Exponent = readBlock();
        param.D = readBlock();
        param.P = readBlock();
        param.Q = readBlock();
        param.DP = readBlock();
        param.DQ = readBlock();
        param.InverseQ = readBlock();
      }
      else
      {
        throw new Exception("pem需要BEGIN END標頭");
      }

      rsa.ImportParameters(param);
      return rsa;
    }
    static private Regex _PEMCode = new Regex(@"--+.+?--+|\s+");
    static private byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
    static private byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 };


    /// <summary>
    /// 將RSA中的密鑰對轉換成PEM格式,usePKCS8=false時返回PKCS#1格式,否則返回PKCS#8格式,如果convertToPublic含私鑰的RSA將隻返回公鑰,僅含公鑰的RSA不受影響
    /// </summary>
    public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, bool usePKCS8)
    {
      //https://www.jianshu.com/p/25803dd9527d
      //https://www.cnblogs.com/ylz8401/p/8443819.html
      //https://blog.csdn.net/jiayanhui2877/article/details/47187077
      //https://blog.csdn.net/xuanshao_/article/details/51679824
      //https://blog.csdn.net/xuanshao_/article/details/51672547

      var ms = new MemoryStream();
      //寫入一個長度字節碼
      Action<int> writeLenByte = (len) =>
      {
        if (len < 0x80)
        {
          ms.WriteByte((byte)len);
        }
        else if (len <= 0xff)
        {
          ms.WriteByte(0x81);
          ms.WriteByte((byte)len);
        }
        else
        {
          ms.WriteByte(0x82);
          ms.WriteByte((byte)(len >> 8 & 0xff));
          ms.WriteByte((byte)(len & 0xff));
        }
      };
      //寫入一塊數據
      Action<byte[]> writeBlock = (byts) =>
      {
        var addZero = (byts[0] >> 4) >= 0x8;
        ms.WriteByte(0x02);
        var len = byts.Length + (addZero ? 1 : 0);
        writeLenByte(len);

        if (addZero)
        {
          ms.WriteByte(0x00);
        }
        ms.Write(byts, 0, byts.Length);
      };
      //根據後續內容長度寫入長度數據
      Func<int, byte[], byte[]> writeLen = (index, byts) =>
      {
        var len = byts.Length - index;

        ms.SetLength(0);
        ms.Write(byts, 0, index);
        writeLenByte(len);
        ms.Write(byts, index, len);

        return ms.ToArray();
      };


      if (rsa.PublicOnly || convertToPublic)
      {
        /****生成公鑰****/
        var param = rsa.ExportParameters(false);


        //寫入總字節數,不含本段長度,額外需要24字節的頭,後續計算好填入
        ms.WriteByte(0x30);
        var index1 = (int)ms.Length;

        //固定內容
        // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        ms.writeAll(_SeqOID);

        //從0x00開始的後續長度
        ms.WriteByte(0x03);
        var index2 = (int)ms.Length;
        ms.WriteByte(0x00);

        //後續內容長度
        ms.WriteByte(0x30);
        var index3 = (int)ms.Length;

        //寫入Modulus
        writeBlock(param.Modulus);

        //寫入Exponent
        writeBlock(param.Exponent);


        //計算空缺的長度
        var byts = ms.ToArray();

        byts = writeLen(index3, byts);
        byts = writeLen(index2, byts);
        byts = writeLen(index1, byts);


        return "-----BEGIN PUBLIC KEY-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END PUBLIC KEY-----";
      }
      else
      {
        /****生成私鑰****/
        var param = rsa.ExportParameters(true);

        //寫入總字節數,後續寫入
        ms.WriteByte(0x30);
        int index1 = (int)ms.Length;

        //寫入版本號
        ms.writeAll(_Ver);

        //PKCS8 多一段數據
        int index2 = -1, index3 = -1;
        if (usePKCS8)
        {
          //固定內容
          ms.writeAll(_SeqOID);

          //後續內容長度
          ms.WriteByte(0x04);
          index2 = (int)ms.Length;

          //後續內容長度
          ms.WriteByte(0x30);
          index3 = (int)ms.Length;

          //寫入版本號
          ms.writeAll(_Ver);
        }

        //寫入數據
        writeBlock(param.Modulus);
        writeBlock(param.Exponent);
        writeBlock(param.D);
        writeBlock(param.P);
        writeBlock(param.Q);
        writeBlock(param.DP);
        writeBlock(param.DQ);
        writeBlock(param.InverseQ);


        //計算空缺的長度
        var byts = ms.ToArray();

        if (index2 != -1)
        {
          byts = writeLen(index3, byts);
          byts = writeLen(index2, byts);
        }
        byts = writeLen(index1, byts);


        var flag = " PRIVATE KEY";
        if (!usePKCS8)
        {
          flag = " RSA" + flag;
        }
        return "-----BEGIN" + flag + "-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END" + flag + "-----";
      }
    }
  }

以上就是詳解c#與js的rsa加密互通的詳細內容,更多關於c#與js的rsa加密互通的資料請關註WalkonNet其它相關文章!

推薦閱讀:

    None Found