天天看點

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 + "-----";
            }
        }
    }      

繼續閱讀