知足者長樂也

像雄鷹一樣在水中飛翔
個人資料
正文

c#與JavaScript實現對用戶名、密碼進行RSA非對稱加密

(2017-04-03 08:12:19) 下一個

博主最近手上這個項目呢(就是有上百個萬惡的複雜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             try25             {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= key21             };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.LetterIntentCount43                 };44             }45             else46             {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#解密的基本功能。

對安全性這一塊一直沒什麽研究,這次的項目又不要求...  

一直想學習學習大神們對於整個安全性做的操作,在博客園找了很久也沒找到特別詳細通俗易懂的....

在此,也懇請各位大神能分享一下自己這方麵的經驗,感激不盡。

 

原創文章,代碼都是從自己項目裏貼出來的。轉載請注明出處哦,親~~~

[ 打印 ]
閱讀 ()評論 (0)
評論
目前還沒有任何評論
登錄後才可評論.