c#与JavaScript实现对用户名、密码进行RSA非对称加密
博主最近手上这个项目呢(就是有上百个万恶的复杂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#对应的两个基础方法
- /// <summary>
- /// 产生一组RSA公钥、私钥
- /// </summary>
- /// <returns></returns>
- public static Dictionary<string, string> CreateRsaKeyPair()
- {
- var keyPair = new Dictionary<string, string>();
- var rsaProvider = new RSACryptoServiceProvider();
- RSAParameters parameter = rsaProvider.ExportParameters(true);
- keyPair.Add("PUBLIC", BytesToHexString(parameter.Exponent) + "," + BytesToHexString(parameter.Modulus));
- keyPair.Add("PRIVATE", rsaProvider.ToXmlString(true));
- return keyPair;
- }
- /// <summary>
- /// RSA解密字符串
- /// </summary>
- /// <param name="encryptData">密文</param>
- /// <param name="privateKey">私钥</param>
- /// <returns>明文</returns>
- public static string DecryptRSA(string encryptData, string privateKey)
- {
- string decryptData = "";
- try
- {
- var provider = new RSACryptoServiceProvider();
- provider.FromXmlString(privateKey);
- byte[] result = provider.Decrypt(HexStringToBytes(encryptData), false);
- ASCIIEncoding enc = new ASCIIEncoding();
- decryptData = enc.GetString(result);
- }
- catch (Exception e)
- {
- throw new Exception("RSA解密出错!", e);
- }
- return decryptData;
- }
- private static string BytesToHexString(byte[] input)
- {
- StringBuilder hexString = new StringBuilder();
- for (int i = ; i < input.Length; i++)
- {
- hexString.Append(String.Format("{0:X2}", input[i]));
- }
- return hexString.ToString();
- }
- public static byte[] HexStringToBytes(string hex)
- {
- if (hex.Length == )
- {
- return new byte[] { };
- }
- if (hex.Length % == )
- {
- hex = "" + hex;
- }
- byte[] result = new byte[hex.Length / ];
- for (int i = ; i < hex.Length / ; i++)
- {
- result[i] = byte.Parse(hex.Substring( * i, ), System.Globalization.NumberStyles.AllowHexSpecifier);
- }
- return result;
- }
注:
两个私有方法是进行16进制的转换,因为js前端rsa加密时要求的参数需要是16进制字符串。
其实博主认为比较好的方法是:后台不做转换,直接提供与接收普通字符串,由客户端按自身需要自己做类型转换。
博主这儿客户端就是一个web网站,正好后台以前又有这么两个转换方法,故在后台做了16进制转换。
下面贴出不做转换产生公钥私钥的代码(替换第10/11行代码):
- keyPair.Add("PUBLIC", rsaProvider.ToXmlString(false));
- keyPair.Add("PRIVATE", rsaProvider.ToXmlString(true));
我们还需要一个独立的获取RSA公钥的接口:
- /// <summary>
- /// 获取RSA公钥
- /// </summary>
- /// <returns></returns>
- [Route("api/UC/GetRsaPublicKey")]
- [HttpGet]
- [Anonymous]
- public GetRsaPublicKeyResult GetRsaPublicKey()
- {
- var rsaKeys = Security.CreateRsaKeyPair();
- var key = Guid.NewGuid().ToString();
- //添加RSA密钥到缓存
- CacheDataManager.DataInsert(key, rsaKeys["PRIVATE"], DateTime.Now.AddMinutes());
- return new GetRsaPublicKeyResult()
- {
- Code = ,
- RsaPublicKey = rsaKeys["PUBLIC"],
- Key= key
- };
- }
那么我们的登录接口就该做成这样:
- /// <summary>
- /// 用户登录
- /// RSA加密密码
- /// </summary>
- /// <returns></returns>
- [Route("api/UC/Login")]
- [HttpPost]
- [Anonymous]
- public LoginResult Login([FromBody] LoginModel loginModel)
- {
- var privateKey = CacheDataManager.GetPrivateKey(loginModel.key);
- if (!string.IsNullOrEmpty(privateKey))
- {
- // 移除缓存
- CacheDataManager.RemoveKey(privateKey);
- if (string.IsNullOrEmpty(loginModel.phoneNumber))
- {
- throw new UserDisplayException("请输入用户名!");
- }
- if (string.IsNullOrEmpty(loginModel.password))
- {
- throw new UserDisplayException("请输入密码!");
- }
- var password = Security.DecryptRSA(loginModel.password, privateKey);
- loginModel.password = password;
- var result = accountInfoService.User_Login(loginModel.phoneNumber, loginModel.password, loginModel.userType);
- // 产生令牌
- var token = CreateToken(result.UUID, loginModel.userType.ToString());
- return new LoginResult()
- {
- Code = ,
- UserPrefect = result.UserPrefect,
- Token = token,
- IMName = result.IMName,
- IMPassword = result.IMPassword,
- LetterIntentCount = result.LetterIntentCount
- };
- }
- else
- {
- throw new Exception("非法密钥key值!");
- }
- }
注:
1.我们需要客户端回传key值,以确认该用户使用公钥对应密钥
2.对密文进行RSA解密
那么我们再来看看前端界面应该怎么做
1.我们需要三个文件:Barrett.js BigInt.js RSA.js
下载地址:http://download.csdn.net/detail/cb511612371/9202207
2.在引用jquery后添加对三个文件的引用
- <script src="Libs/jquery/jquery-1.8.3.js"></script>
- <script src="Libs/jquery.cookie.js"></script>
- <script src="Libs/BigInt.js"></script>
- <script src="Libs/RSA.js"></script>
- <script src="Libs/Barrett.js"></script>
3.写一个获取公钥的js方法到common.js文件(尽量将这个方法的调用放在用户不经意之间,在用户触发登录事件之前执行一次,避免等到用户点击登录的时候再调用造成停顿 )
- // 获取RSA公钥
- var getPublicKey=function () {
- if(getCookie("publicKey")==null){
- $.ajax({
- url: "/api/UC/GetRsaPublicKey",
- type: "get",
- contentType: "application/x-www-form-urlencoded; charset=utf-8",
- async: false,
- data: {},
- dataType: "json",
- success: function (data) {
- if (data.Code == ) {
- var publicKey = data.RsaPublicKey + "," + data.Key;
setCookie("publicKey", publicKey,8);// 此处存储时间应该小于后台缓存时间
return publicKey;- } else {
- Config.Method.JudgeCode(data, );
- }
- }
- });
}else{
return getCookie("publicKey");
}- }
4.写一个通用的js加密方法到common.js
- // RSA加密
- var rsaEncrypt: function (pwd) {
- var publicKey=getPublicKey();
- setMaxDigits();
- var rsaKey = new RSAKeyPair(publicKey.split(",")[], "", publicKey.split(",")[]);
- var pwdRtn = encryptedString(rsaKey, pwd);
- return pwdRtn+","+publicKey.split(",")[2];
- },
5.来看看我们在登录按钮的js方法中具体调用:
- var userName = $(".rencaibao_login_regist .login .userName").val();
- var pwd = $(".rencaibao_login_regist .login .password").val();
- if (!userName.length) {
- alert("用户名不能为空!");
- return;
- }
- if (!pwd.length) {
- alert("密码不能为空!");
- return;
- }
- if (!Config.Datas.RegPhone.test(userName)) {
- alert("请输入正确的手机号!");
- return;
- }
- if (!Config.Datas.PasswordVerification.test(pwd)) {
- alert("密码格式错误!");
- return;
- }
- var pwd1 = Config.Method.rsaEncrypt(pwd);
- $.post(Config.Api.UC.Login, { 'phoneNumber': userName, 'password': pwd1.split(",")[0], 'userType': "Enterprise", 'key': publicKey.split(",")[1] }, function (data) {
- publicKey = "";
- if (data.Code == ) {
- Config.Method.SetCookies(data.Token, userName, data.UserPrefect, data.LetterIntentCount);
- $(".right_yixianghan a .imgDiv").html(data.LetterIntentCount);
- _login_registEvent();
- Config.Method.InitLoginInfo();
- } else {
- Config.Method.JudgeCode(data, );
- }
- });
OK,至此我们就简单的实现了用JS进行RSA加密,c#解密的基本功能。
对安全性这一块一直没什么研究,这次的项目又不要求...
一直想学习学习大神们对于整个安全性做的操作,在博客园找了很久也没找到特别详细通俗易懂的....
在此,也恳请各位大神能分享一下自己这方面的经验,感激不尽。
原创文章,代码都是从自己项目里贴出来的。转载请注明出处哦,亲~~~
c#与JavaScript实现对用户名、密码进行RSA非对称加密的更多相关文章
- javascript版前端页面RSA非对称加密解密
最近由于项目需要做一个url传参,并在页面显示参数内容的需求,这样就会遇到一个url地址可能会被假冒, 并传递非法内容显示在页面的尴尬情况 比如xxx.shtml?server=xxx是坏人& ...
- Jquery 实现 “下次自动登录” 记住用户名密码功能
转载自:http://blog.csdn.net/aspnet_lyc/article/details/12030039?utm_source=tuicool&utm_medium=refer ...
- 验证码的设计与记住我存储用户名密码cookie的技术及单选按钮选择登录人身份的实现
login.jsp页面 <head> <script type="text/javascript" src="js/captcha.js"&g ...
- Javascript登录页面“记住密码”实现
JS记住密码实现效果: JavaScript Code 1234567891011121314151617181920212223242526272829303132 <!DOCTYPE ...
- cookie记住用户名密码
<script src="js/jquery.cookie.js" type="text/javascript"></script> $ ...
- Django用户名密码错误提示
from django.shortcuts import render # Create your views here. from django.shortcuts import render fr ...
- 【WCF】使用“用户名/密码”验证的合理方法
我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...
- C# 用SoapUI调试WCF服务接口(WCF中包含用户名密码的验证)
问题描述: 一般调试wcf程序可以直接建一个单元测试,直接调接口. 但是,这次,我还要测试在接口内的代码中看接收到的用户名密码是否正确,所以,单一的直接调用接口方法行不通, 然后就想办法通过soapU ...
- [No00008F]PLSQL自动登录,记住用户名密码&日常使用技巧
配置启动时的登录用户名和密码 这是个有争议的功能,因为记住密码会给带来数据安全的问题. 但假如是开发用的库,密码甚至可以和用户名相同,每次输入密码实在没什么意义,可以考虑让PLSQL Develope ...
随机推荐
- [Quartz笔记]玩转定时调度
简介 Quartz是什么? Quartz是一个特性丰富的.开源的作业调度框架.它可以集成到任何Java应用. 使用它,你可以非常轻松的实现定时任务的调度执行. Quartz的应用场景 场景1:提醒和告 ...
- Open DS
0: 1. Develope OpenDS from here: eu.opends.main --> Simulator.java --> main()
- java集合你了解多少?
用了java集合这么久,还没有系统的研究过java的集合结构,今天亲自画了下类图,总算有所收获. 一.所有集合都实现了Iterable接口. Iterable接口中包含一个抽象方法:Iterator& ...
- C++_系列自学课程_第_11_课_类型转换_《C++ Primer 第四版》
上次说了关于表达式的一些内容,说到还有一些关于数据类型转换的内容,今天我们接着八一八C++中的数据类型转换. 一.隐式类型转换 在表达式中,有些操作符可以对多种类型的操作数进行操作, 例如 + 操作符 ...
- JVM之内存结构
JVM是按照运行时数据的存储结构来划分内存结构的.JVM在运行Java程序时,将他们划分成不同格式的数据,分别存储在不同的区域,这些数据就是运行时数据.运行时数据区域包括堆,方法区,运行时常量池,程序 ...
- CSS常用渐变
边框渐变: border-image: -webkit-linear-gradient( red , blue) 30 30; border-image: -moz-linear-gradient( ...
- call_user_func()的参数不能为引用传递 自定义替代方法
php手册 中关于 请注意,传入call_user_func()的参数不能为引用传递. 关于这个情况的解释,可自己搜索.我们可以自己定义一个函数解决这样的问题,实例如下: <?php ini_s ...
- html5上传图片(二)一解决部分手机拍照上传图片转向问题
本以为解决跨域上传后没有问题了,不成想被测试找出一个问题,那就是在手机上拍照上传后图片会旋转.很头痛,不过没有办法,问题还是需要解决的.在查阅了一系列资料后我找到了相应的解决方案,利用exif.js获 ...
- Java学习-序列化
参考资料: http://www.2cto.com/kf/201405/305380.html http://www.cnblogs.com/xdp-gacl/p/3777987.html 序列化 ...
- 便于开发的Helper类
一.将config封装实体层: 例子config: <?xml version="1.0" encoding="utf-8" ?> <Sett ...