C# javascript js RSA非对称加解密的实现

RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用.
RSA算法是第一个能同时用于加密和数字签名的算法.
也易于理解和操作。RSA是被研究得最广泛的公钥算法,从提出到现今的三十多年里,经历了各种攻击的考验,逐渐为人们接受,截止2017年被普遍认为是最优秀的公钥方案之一

已公开的或已知的攻击方法编辑
1,针对RSA最流行的攻击一般是基于大数因数分解。1999年,RSA-155 (512 bits)被成功分解,花了五个月时间(约8000 MIPS年)和224 CPU hours在一台有3.2G中央内存的Cray C916计算机上完成。

2,秀尔算法
量子计算里的秀尔算法能使穷举的效率大大的提高。由于RSA算法是基于大数分解(无法抵抗穷举攻击),因此在未来量子计算能对RSA算法构成较大的威胁。一个拥有N量子比特的量子计算机,每次可进行2^N次运算,理论上讲,密钥为1024位长的RSA算法,用一台512量子比特位的量子计算机在1秒内即可破解。

当然在目前2019年 量子技术还不成熟的情况下. 我们暂时先不考虑安全性, 因为大多数的加密行为保护的都是没有多大价值的信息. 真正值得保护的高价值信息. 会有很多套算法一起用. 破解成本远超过所得收益.这就够了…

网上流行的方法是RSA 的密钥是固定,估计很多程序员直接就用了网上的密钥…这样是极度不安全的. 为了安全我们必须自己生成RSA密钥.

首先来看主程序C#端
RSACryptoHelper.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace Common
{
    /// <summary>
    /// RAS加解密算法 
    /// 先用 RSACryptoHelper.CreateKey(); 创建一对密钥,第一个是私钥,第二个是公钥. 公钥发给客户端,客户端用js和公钥加密,密文到服务器 用私钥解密
    /// 对应的前台js加密算法库为jsencrypt.js 
    /// 参考 https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js
    /// 参考 https://blog.csdn.net/zhangjianying/article/details/79873392
    /// </summary>
    /// <example>
    /// 
    /// var keys= RSACryptoHelper.CreateKey();//创建一对密钥,第一个是私钥,第二个是公钥.公钥发给客户端,密文到服务器 用私钥解密
    /// 
    /// var miwen =   RSACryptoHelper.RSA_Encrypt("被加密的文本",keys[0]);//公钥加密
    /// var yuanwen = RSACryptoHelper.RSA_Decrypt(miwen,keys[1]);//私钥解密
    /// 
    /// </example> 

    public static class RSACryptoHelper
    { 

        /// <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 = Convert.ToBase64String(sp.ExportCspBlob(true));//private key
            string publicKey = 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>
        /// 加密 用的是PEM格式的密钥
        /// </summary>
        /// <param name="str_Plain_Text">要加密的数据</param>
        /// <param name="str_Public_PEMKey">Xml格式的公钥</param>
        /// <returns></returns>
        public static string Encrypt_PEMKey(string str_Plain_Text, string str_Public_PEMKey)
        {
            using (RSACryptoServiceProvider RSA = RSA_PEM.FromPEM(str_Public_PEMKey))
            {  
                return Encrypt(str_Plain_Text, RSA);
            }
        }

        /// <summary>
        /// 加密 用的是Xml格式的密钥
        /// </summary>
        /// <param name="str_Plain_Text">要加密的数据</param>
        /// <param name="str_Public_XmlKey">Xml格式的公钥</param>
        /// <returns></returns>
        public static string Encrypt_XmlKey(string str_Plain_Text, string str_Public_XmlKey)
        {
            using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
            {

                RSA.FromXmlString(str_Public_XmlKey);//载入公钥

                return Encrypt(str_Plain_Text, RSA);
            } 
        }

        private static string Encrypt(string str_Plain_Text, RSACryptoServiceProvider RSA)
        {
            var data = Encoding.UTF8.GetBytes(str_Plain_Text);
            int buffersize = (RSA.KeySize / 8) - 11;
            var buffer = new byte[buffersize];
            using (MemoryStream input = new MemoryStream(data), output = new MemoryStream())
            {
                while (true)
                {
                    int readsize = input.Read(buffer, 0, buffersize);
                    if (readsize <= 0)
                    {
                        break;
                    }
                    var temp = new byte[readsize];
                    Array.Copy(buffer, 0, temp, 0, readsize);
                    var EncBytes = RSA.Encrypt(temp, false);
                    output.Write(EncBytes, 0, EncBytes.Length);
                }
                return Convert.ToBase64String(output.ToArray());
            }
        }


        /// <summary>
        /// 解密 用的是Xml格式的密钥
        /// </summary>
        /// <param name="str_Cypher_Text">密文</param>
        /// <param name="str_Private_Key">密钥</param>
        /// <returns></returns>

        public static string Decrypt_XmlKey(string str_Cypher_Text, string str_Private_XmlKey)
        {
            using (var RSA = new RSACryptoServiceProvider())
            {
                RSA.FromXmlString(str_Private_XmlKey);
                return Decrypt(str_Cypher_Text, RSA);
            }
        }


        /// <summary>
        /// 解密 用的是PEM格式的密钥
        /// </summary>
        /// <param name="str_Cypher_Text">密文</param>
        /// <param name="str_Private_Key">密钥</param>
        /// <returns></returns>

        public static string Decrypt_PEMKey(string str_Cypher_Text, string str_Private_PEMKey)
        {
            //using (var RSA = new RSACryptoServiceProvider())
            using (var RSA = RSA_PEM.FromPEM(str_Private_PEMKey))
            {
                return Decrypt(str_Cypher_Text, RSA);
            } 
        }

        private static string Decrypt(string str_Cypher_Text, RSACryptoServiceProvider RSA)
        {
            var data = Convert.FromBase64String(str_Cypher_Text);

            //RSA.FromXmlString(str_Private_Key);
            int buffersize = RSA.KeySize / 8;
            var buffer = new byte[buffersize];
            using (MemoryStream input = new MemoryStream(data),
                 output = new MemoryStream())
            {
                while (true)
                {
                    int readsize = input.Read(buffer, 0, buffersize);
                    if (readsize <= 0)
                    {
                        break;
                    }

                    var temp = new byte[readsize];
                    Array.Copy(buffer, 0, temp, 0, readsize);
                    var DecBytes = RSA.Decrypt(temp, false);
                    output.Write(DecBytes, 0, DecBytes.Length);
                }
                return Encoding.UTF8.GetString(output.ToArray());
            }
        }
    }
}
  

第二个重要的文件来自国内的一位大神, 他写的PEMS密钥转换代码, 使得C#默认的RSACryptoServiceProvider 算法支持 PEM 密钥. 也就是PKCS#1、PKCS#8格式格式的密钥
他的博客 https://www.cnblogs.com/xiangyuecn/p/9922325.html
他的这个项目地址 https://github.com/xiangyuecn/RSA-csharp

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Common
{
	/// <summary>
	/// RSA PEM格式秘钥对的解析和导出
	/// </summary>
	public class RSA_PEM {
		/// <summary>
		/// 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM
		/// </summary>
		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 + "-----";
			}
		}
	}
}

第三个文件是一个辅助类 RSA_Unit .cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common
{
	/// <summary>
	/// 封装的一些通用方法
	/// </summary>
	public class RSA_Unit {
		static public string Base64EncodeBytes(byte[] byts) {
			return Convert.ToBase64String(byts);
		}
		static public byte[] Base64DecodeBytes(string str) {
			try {
				return 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 LoginController : MK.AppCode.MVC.BaseController
    { 
        // GET: /BaseApp/Login/

        public ActionResult Index()
        {
            CreateRSAKey();
            return View();
        }

        public void CreateRSAKey()
        {
            var RSAKey = Common.RSACryptoHelper.CreateKey_PEM_PKCS1();

            Session["RSAPrivateKey"] = RSAKey[0];//私钥自己存好,等密码回来以后用来解密.
            ViewBag.RSAPublicKey = RSAKey[1]; //公钥发到客户端, 客户端用来加密.

        }
        
		  [HttpPost]  
        public ActionResult Index(string sloginname, string sloginpwd, string securityCode, string RSAKey)
        {
            string RSAPrivateKey = Session["RSAPrivateKey"] as string;
            sloginname = Common.RSACryptoHelper.Decrypt_PEMKey(sloginname, RSAPrivateKey); //为防止黑客在网络传输中 窃取明文用户名和密码
            sloginpwd  = Common.RSACryptoHelper.Decrypt_PEMKey(sloginpwd, RSAPrivateKey);  //为防止黑客在网络传输中 窃取明文用户名和密码

            //刷新新的Key
            CreateRSAKey();
            //....
            //....
        }
}

前台界面则需要引入
项目地址 http://www.travistidwell.com/jsencrypt
github: https://github.com/travist/jsencrypt
也可以直接复制csdn的代码.

在表单提交前.用JSEncrypt 进行加密.

<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
<form id="loginform" action="/BaseApp/Login/Index" method="post"> 
                <textarea id="RSAPublicKey" style="display:none;">@ViewBag.RSAPublicKey</textarea>
                <input type="hidden" id="sloginname" name="sloginname" />
                <input type="hidden" id="sloginpwd" name="sloginpwd" />
                <input type="hidden" id="securityCode" name="securityCode" />

                <div class="form-group has-feedback">
                    <input type="text" class="form-control" id="userName" placeholder="请输入帐号" tabindex="1" onkeydown="gotonext()">
                    <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
                </div>
                <div class="form-group has-feedback">
                    <input type="password" class="form-control" id="password" placeholder="请输入密码" tabindex="2" />
                    <span class="glyphicon glyphicon-lock form-control-feedback"></span>
                </div>

                <div class="form-group has-feedback">
                    <input type="text" name="verifyCode" required style="width:200px;" tabindex="3" />
                    <img src="~/BaseApp/Login/GetSecurityCode" id="secImg" onclick="RefreshIMG()" title="单击刷新" />
                </div>

                <div class="row">


                    <!-- /.col -->
                    <div class="col-xs-12">
                        <button type="submit" class="btn btn-success btn-block btn-flat MKbtn" name="btnLogin" tabindex="4">登录</button>
                    </div>
                    <!-- /.col -->
                </div>


            </form>
            <Scrpt>
 $('#loginform').on("submit", function () {
        debugger;
        var PUBLIC_KEY = $("#RSAPublicKey").val();
        var $userName = $('#userName').val();
        var $password = $('#password').val();
        var $verifyCode = $('input[name="verifyCode"]').val();

        if ($userName == "" || $password == "" || $verifyCode == "")
        {
            alert("用户名、密码、验证码不能为空!");
            return false;
        }
        //使用公钥加密
        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(PUBLIC_KEY);
        $('input[name="sloginname"]').val(encrypt.encrypt($userName));
        $('input[name="sloginpwd"]').val(encrypt.encrypt($password));
        return true;
    }); 
    </script>
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值