博主最近手上這個項目呢(就是有上百個萬惡的複雜excel須要解析的那個項目,參見博客:http://www.cnblogs.com/csqb-511612371/p/4885930.html),因爲是一個內網項目,安全性要求很低,不須要作什麼報文加密。html
可是總以爲用戶名密碼都是明文傳輸,略微有點坑甲方...前端
想了想,那就作個RSA加密,把用戶名、密碼作密文傳輸吧...至於爲何是RSA,由於也想趁機學習一下,DES、MD5什麼的之前都作過了,不想又複製粘貼敷衍了事,怎麼說領導還給了3天時間呢...jquery
咱但是有原則的程序員。git
首先要感謝博客園一些前輩們相關的一些文章,讓博主一個只知道RSA基本概念的人在很短的時間內就成功實現了JS進行加密,C#進行解密的一個過程。程序員
大概看了10來篇文章,感受差很少了纔開始寫的本身的代碼...
很難再具體回憶到從哪一篇文章獲益最大,只能在此統一表示感謝!web
寫代碼以前大概整理出一個總體流程:ajax
0.後臺實現兩個基礎方法:json
(1)CreateRsaKeyPair()方法,產生一對RSA私鑰公鑰,並配以惟一的鍵值keyc#
(2)DecryptRSA()方法,對密文進行RSA解密api
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#解密的基本功能。
對安全性這一塊一直沒什麼研究,此次的項目又不要求...
一直想學習學習大神們對於整個安全性作的操做,在博客園找了好久也沒找到特別詳細通俗易懂的....
在此,也懇請各位大神能分享一下本身這方面的經驗,感激涕零。
原創文章,代碼都是從本身項目裏貼出來的。轉載請註明出處哦,親~~~