博主最近手上这个项目呢(就是有上百个万恶的复杂excel需要解析的那个项目,参见博客:http://www.cnblogs.com/csqb-511612371/p/4885930.html),由于是一个内网项目,安全性要求很低,不需要做什么报文加密。
但是总觉得用户名密码都是明文传输,略微有点坑甲方...
想了想,那就做个RSA加密,把用户名、密码做密文传输吧...至于为什么是RSA,因为也想趁机学习一下,DES、MD5什么的以前都做过了,不想又复制粘贴敷衍了事,怎么说领导还给了3天时间呢...
咱可是有原则的程序员。
首先要感谢博客园一些前辈们相关的一些文章,让博主一个只知道RSA基本概念的人在很短的时间内就成功实现了JS进行加密,C#进行解密的一个过程。
大概看了10来篇文章,感觉差不多了才开始写的自己的代码...
很难再具体回忆到从哪一篇文章获益最大,只能在此统一表示感谢!
写代码之前大概整理出一个整体流程:
0.后台实现两个基础方法:
(1)CreateRsaKeyPair()方法,产生一对RSA私钥公钥,并配以唯一的键值key
(2)DecryptRSA()方法,对密文进行RSA解密
1.用户访问客户端,客户端向服务器请求获取一个RSA公钥以及键值key,存储在本地
2.用户在本地公钥失效前发起登录请求,则使用已有公钥对用户密码进行加密;若已过期则执行1后再加密
3.客户端将密文与key一起传回后台
4.后台通过key找到缓存里面的私钥,对密文进行解密
OK,我们先来看看c#对应的两个基础方法
1 /// <summary>
2 /// 产生一组RSA公钥、私钥
3 /// </summary>
4 /// <returns></returns>
5 public static Dictionary<string, string> CreateRsaKeyPair()
6 {
7 var keyPair = new Dictionary<string, string>();
8 var rsaProvider = new RSACryptoServiceProvider(1024);
9 RSAParameters parameter = rsaProvider.ExportParameters(true);
10 keyPair.Add("PUBLIC", BytesToHexString(parameter.Exponent) + "," + BytesToHexString(parameter.Modulus));
11 keyPair.Add("PRIVATE", rsaProvider.ToXmlString(true));
12 return keyPair;
13 }
14
15 /// <summary>
16 /// RSA解密字符串
17 /// </summary>
18 /// <param name="encryptData">密文</param>
19 /// <param name="privateKey">私钥</param>
20 /// <returns>明文</returns>
21 public static string DecryptRSA(string encryptData, string privateKey)
22 {
23 string decryptData = "";
24 try
25 {
26 var provider = new RSACryptoServiceProvider();
27 provider.FromXmlString(privateKey);
28
29 byte[] result = provider.Decrypt(HexStringToBytes(encryptData), false);
30 ASCIIEncoding enc = new ASCIIEncoding();
31 decryptData = enc.GetString(result);
32 }
33 catch (Exception e)
34 {
35 throw new Exception("RSA解密出错!", e);
36 }
37 return decryptData;
38 }
39
40 private static string BytesToHexString(byte[] input)
41 {
42 StringBuilder hexString = new StringBuilder(64);
43
44 for (int i = 0; i < input.Length; i++)
45 {
46 hexString.Append(String.Format("{0:X2}", input[i]));
47 }
48 return hexString.ToString();
49 }
50
51 public static byte[] HexStringToBytes(string hex)
52 {
53 if (hex.Length == 0)
54 {
55 return new byte[] { 0 };
56 }
57
58 if (hex.Length % 2 == 1)
59 {
60 hex = "0" + hex;
61 }
62
63 byte[] result = new byte[hex.Length / 2];
64
65 for (int i = 0; i < hex.Length / 2; i++)
66 {
67 result[i] = byte.Parse(hex.Substring(2 * i, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
68 }
69
70 return result;
71 }
注:
两个私有方法是进行16进制的转换,因为js前端rsa加密时要求的参数需要是16进制字符串。
其实博主认为比较好的方法是:后台不做转换,直接提供与接收普通字符串,由客户端按自身需要自己做类型转换。
博主这儿客户端就是一个web网站,正好后台以前又有这么两个转换方法,故在后台做了16进制转换。
下面贴出不做转换产生公钥私钥的代码(替换第10/11行代码):
1 keyPair.Add("PUBLIC", rsaProvider.ToXmlString(false));
2 keyPair.Add("PRIVATE", rsaProvider.ToXmlString(true));
我们还需要一个独立的获取RSA公钥的接口:
1 /// <summary>
2 /// 获取RSA公钥
3 /// </summary>
4 /// <returns></returns>
5 [Route("api/UC/GetRsaPublicKey")]
6 [HttpGet]
7 [Anonymous]
8 public GetRsaPublicKeyResult GetRsaPublicKey()
9 {
10 var rsaKeys = Security.CreateRsaKeyPair();
11
12 var key = Guid.NewGuid().ToString();
13 //添加RSA密钥到缓存
14 CacheDataManager.DataInsert(key, rsaKeys["PRIVATE"], DateTime.Now.AddMinutes(10));
15
16 return new GetRsaPublicKeyResult()
17 {
18 Code = 0,
19 RsaPublicKey = rsaKeys["PUBLIC"],
20 Key= key
21 };
22 }
那么我们的登录接口就该做成这样:
1 /// <summary>
2 /// 用户登录
3 /// RSA加密密码
4 /// </summary>
5 /// <returns></returns>
6 [Route("api/UC/Login")]
7 [HttpPost]
8 [Anonymous]
9 public LoginResult Login([FromBody] LoginModel loginModel)
10 {
11 var privateKey = CacheDataManager.GetPrivateKey(loginModel.key);
12 if (!string.IsNullOrEmpty(privateKey))
13 {
14 // 移除缓存
15 CacheDataManager.RemoveKey(privateKey);
16
17 if (string.IsNullOrEmpty(loginModel.phoneNumber))
18 {
19 throw new UserDisplayException("请输入用户名!");
20 }
21
22 if (string.IsNullOrEmpty(loginModel.password))
23 {
24 throw new UserDisplayException("请输入密码!");
25 }
26
27 var password = Security.DecryptRSA(loginModel.password, privateKey);
28 loginModel.password = password;
29
30 var result = accountInfoService.User_Login(loginModel.phoneNumber, loginModel.password, loginModel.userType);
31
32 // 产生令牌
33 var token = CreateToken(result.UUID, loginModel.userType.ToString());
34
35 return new LoginResult()
36 {
37 Code = 0,
38 UserPrefect = result.UserPrefect,
39 Token = token,
40 IMName = result.IMName,
41 IMPassword = result.IMPassword,
42 LetterIntentCount = result.LetterIntentCount
43 };
44 }
45 else
46 {
47 throw new Exception("非法密钥key值!");
48 }
49 }
注:
1.我们需要客户端回传key值,以确认该用户使用公钥对应密钥
2.对密文进行RSA解密
那么我们再来看看前端界面应该怎么做
1.我们需要三个文件:Barrett.js BigInt.js RSA.js
下载地址:http://download.csdn.net/detail/cb511612371/9202207
2.在引用jquery后添加对三个文件的引用
1 <script src="Libs/jquery/jquery-1.8.3.js"></script> 2 <script src="Libs/jquery.cookie.js"></script> 3 <script src="Libs/BigInt.js"></script> 4 <script src="Libs/RSA.js"></script> 5 <script src="Libs/Barrett.js"></script>
3.写一个获取公钥的js方法到common.js文件(尽量将这个方法的调用放在用户不经意之间,在用户触发登录事件之前执行一次,避免等到用户点击登录的时候再调用造成停顿 )
1 // 获取RSA公钥
2 var getPublicKey=function () {
3 if(getCookie("publicKey")==null){
4 $.ajax({
5 url: "/api/UC/GetRsaPublicKey",
6 type: "get",
7 contentType: "application/x-www-form-urlencoded; charset=utf-8",
8 async: false,
9 data: {},
10 dataType: "json",
11 success: function (data) {
12 if (data.Code == 0) {
13 var publicKey = data.RsaPublicKey + "," + data.Key; setCookie("publicKey", publicKey,8);// 此处存储时间应该小于后台缓存时间 return publicKey;
14 } else {
15 Config.Method.JudgeCode(data, 1);
16 }
17 }
18 }); }else{ return getCookie("publicKey"); }
19 }
4.写一个通用的js加密方法到common.js
1 // RSA加密
2 var rsaEncrypt: function (pwd) {
3 var publicKey=getPublicKey();
4 setMaxDigits(129);
5 var rsaKey = new RSAKeyPair(publicKey.split(",")[0], "", publicKey.split(",")[1]);
6 var pwdRtn = encryptedString(rsaKey, pwd);
7 return pwdRtn+","+publicKey.split(",")[2];
8 },
5.来看看我们在登录按钮的js方法中具体调用:
1 var userName = $(".rencaibao_login_regist .login .userName").val();
2 var pwd = $(".rencaibao_login_regist .login .password").val();
3 if (!userName.length) {
4 alert("用户名不能为空!");
5 return;
6 }
7 if (!pwd.length) {
8 alert("密码不能为空!");
9 return;
10 }
11 if (!Config.Datas.RegPhone.test(userName)) {
12 alert("请输入正确的手机号!");
13 return;
14 }
15 if (!Config.Datas.PasswordVerification.test(pwd)) {
16 alert("密码格式错误!");
17 return;
18 }
19
20 var pwd1 = Config.Method.rsaEncrypt(pwd);
21
22 $.post(Config.Api.UC.Login, { 'phoneNumber': userName, 'password': pwd1.split(",")[0], 'userType': "Enterprise", 'key': publicKey.split(",")[1] }, function (data) {
23 publicKey = "";
24 if (data.Code == 0) {
25 Config.Method.SetCookies(data.Token, userName, data.UserPrefect, data.LetterIntentCount);
26 $(".right_yixianghan a .imgDiv").html(data.LetterIntentCount);
27 _login_registEvent();
28 Config.Method.InitLoginInfo();
29 } else {33 Config.Method.JudgeCode(data, 0);
34 }
35 });
OK,至此我们就简单的实现了用JS进行RSA加密,c#解密的基本功能。
对安全性这一块一直没什么研究,这次的项目又不要求...
一直想学习学习大神们对于整个安全性做的操作,在博客园找了很久也没找到特别详细通俗易懂的....
在此,也恳请各位大神能分享一下自己这方面的经验,感激不尽。
原创文章,代码都是从自己项目里贴出来的。转载请注明出处哦,亲~~~
来源:http://www.cnblogs.com/csqb-511612371/p/4898437.html