Rsa加密算法應(yīng)用于web登錄密碼加密傳輸

通常我們做一個(gè)Web應(yīng)用程序的時(shí)候都需要登錄,登錄就要輸入用戶名和登錄密碼,并且,用戶名和登錄密碼都是明文傳輸?shù)?,這樣就有可能在中途被別人攔截,尤其是在網(wǎng)吧等場(chǎng)合。

很多安全要求較高的網(wǎng)站都不會(huì)明文傳輸密碼,它們會(huì)使用https來確保傳輸過程的安全,https是用證書來實(shí)現(xiàn)的,證書來自于證書頒發(fā)機(jī)構(gòu),當(dāng)然了,你也可以自己造一張證書,但這樣別人訪問你的網(wǎng)站的時(shí)候還是會(huì)遇到麻煩,因?yàn)槟阕约涸斓淖C書不在用戶瀏覽器的信任范圍之內(nèi),你還得在用戶瀏覽器上安裝你的證書,來讓用戶瀏覽器相信你的網(wǎng)站,很多用戶并不知道如何操作,就算會(huì)操作,也能也不樂意干;另一種選擇是你向權(quán)威證書頒發(fā)機(jī)構(gòu)申請(qǐng)一張證書,但這樣有一定的門檻,還需要付費(fèi),也不是我們樂意干的事。

所以,我們可以用密碼加密傳輸方法。

這里使用了RSA非對(duì)稱加密算法,對(duì)稱加密也許大家都已經(jīng)很熟悉,也就是加密和解密用的都是同樣的密鑰,沒有密鑰,就無法解密,這是對(duì)稱加密。而非對(duì)稱加密算法中,加密所用的密鑰和解密所用的密鑰是不相同的:你使用我的公鑰加密,我使用我的私鑰來解密;如果你不使用我的公鑰加密,那我無法解密;如果我沒有私鑰,我也沒法解密。

這個(gè)登錄密碼加密傳輸方法的原理圖如下:

Rsa加密算法應(yīng)用于web登錄密碼加密傳輸

首先,先演練一下非對(duì)稱加密:

[csharp] view plain copy

1. static void Main(string[] args)
2. {
3. //用于字符串和byte[]之間的互轉(zhuǎn)
4. UTF8Encoding utf8encoder = new UTF8Encoding();
5.
6. //產(chǎn)生一對(duì)公鑰私鑰
7. RSACryptoServiceProvider rsaKeyGenerator = new RSACryptoServiceProvider(1024);
8. string publickey = rsaKeyGenerator.ToXmlString(false);
9. string privatekey = rsaKeyGenerator.ToXmlString(true);
10.
11. //使用公鑰加密密碼
12. RSACryptoServiceProvider rsaToEncrypt = new RSACryptoServiceProvider();
13. rsaToEncrypt.FromXmlString(publickey);
14. string strPassword = "@123#abc$";
15. Console.WriteLine("The original password is: {0}", strPassword);
16. byte[] byEncrypted = rsaToEncrypt.Encrypt(utf8encoder.GetBytes(strPassword), false);
17. Console.Write("Encoded bytes: ");
18. foreach (Byte b in byEncrypted)
19. {
20. Console.Write("{0}", b.ToString("X"));
21. }
22. Console.Write("\n");
23. Console.WriteLine("The encrypted code length is: {0}", byEncrypted.Length);
24.
25. //解密
26. RSACryptoServiceProvider rsaToDecrypt = new RSACryptoServiceProvider();
27. rsaToDecrypt.FromXmlString(privatekey);
28. byte[] byDecrypted = rsaToDecrypt.Decrypt(byEncrypted, false);
29. string strDecryptedPwd = utf8encoder.GetString(byDecrypted);
30. Console.WriteLine("Decrypted Password is: {0}", strDecryptedPwd);
31. }

大家可以清楚看到,密碼被加密成128字節(jié)長(zhǎng)度的密文,為什么是固定128字節(jié)呢?這是因?yàn)槲覀兊腞SACryptoServiceProvider默認(rèn)生成的key的長(zhǎng)度是1024,即1024位的加密,所以不管你要加密的密碼有多長(zhǎng),它生成的密文的長(zhǎng)度肯定是128字節(jié),也因?yàn)檫@樣,密碼的長(zhǎng)度是有限制的,1024位的RSA算法,只能加密大約100個(gè)字節(jié)長(zhǎng)度的明文,要提高可加密的明文的長(zhǎng)度限制,就得增加key的長(zhǎng)度,比如把key改到2048位,這樣能加密的明文的長(zhǎng)度限制也就變?yōu)榇蟾?00出頭這樣……還是太少啊!而且這樣會(huì)帶來加密速度的顯著下降,RSA本來就很慢……是的,比同沒有長(zhǎng)度限制的對(duì)稱加密,這種非對(duì)稱加密的限制可真多,即便是200個(gè)字符,又能傳輸什么東西呢?——密碼!這個(gè)就夠了,傳輸完密碼之后,我們就使用對(duì)稱加密,所以,RSA往往是用來“協(xié)商”一個(gè)對(duì)稱加密的key的。

接下去,真正的難點(diǎn)在于用JavaScript實(shí)現(xiàn)一個(gè)和.net的RSA兼容的算法。代碼:

1.
2. <html xmlns="http://www.w3.org/1999/xhtml">
3. <head runat="server">
4. <title>RSA Login Test</title>
5. <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
6. <script src="Scripts/jQuery.md5.js" type="text/javascript" ></script>
7. <script src="Scripts/BigInt.js" type="text/javascript"></script>
8. <script src="Scripts/RSA.js" type="text/javascript"></script>
9. <script src="Scripts/Barrett.js" type="text/javascript"></script>
10. <script type="text/javascript">
11. function cmdEncrypt() {
12. setMaxDigits(129);
13. var key = new RSAKeyPair("<%=strPublicKeyExponent%>", "", "<%=strPublicKeyModulus%>");
14. var pwdMD5Twice = $.md5($.md5($("#txtPassword").attr("value")));
15. var pwdRtn = encryptedString(key, pwdMD5Twice);
16. $("#encrypted_pwd").attr("value", pwdRtn);
17. $("#formLogin").submit();
18. return;
19. }
20. </script>
21.
22. </head>
23. <body>
24. <form action="Default.aspx" id="formLogin" method="post">
25. <div>
26. <div>
27. User Name:
28. </div>
29. <div>
30. <input id="txtUserName" name="txtUserName" value="<%=postbackUserName%>" type="text" maxlength="16" />
31. </div>
32. <div>
33. Password:
34. </div>
35. <div>
36. <input id="txtPassword" type="password" maxlength="16" />
37. </div>
38. <div>
39. <input id="btnLogin" type="button" value="Login" onclick="return cmdEncrypt()" />
40. </div>
41. </div>
42. <div>
43. <input type="hidden" name="encrypted_pwd" id="encrypted_pwd" />
44. </div>
45. </form>
46. <div>
47. <%=LoginResult%>
48. </div>
49. </body>
50. </html>

這是客戶端代碼,大家可以看到,基本沒有什么服務(wù)器端代碼,< %=postbackUserName%>用于回顯輸入的用戶名,<%=LoginResult%>用于顯示登錄結(jié)果,< %=strPublicKeyExponent%>和<%=strPublicKeyModulus%>則用來告訴客戶端RSA公鑰。需要的javascript文件說明:

? jQuery.md5.js - 用于對(duì)密碼進(jìn)行兩次md5加密;(我通常在數(shù)據(jù)庫中保存的用戶密碼是兩次MD5后的結(jié)果)
? BigInt.js - 用于生成一個(gè)大整型;(這是RSA算法的需要)
? RSA.js - RSA的主要算法;
? Barrett.js - RSA算法所需要用到的一個(gè)支持文件;
1. protected void Page_Load(object sender, EventArgs e)
2. {
3. LoginResult = "";
4. RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
5. if (string.Compare(Request.RequestType, "get", true)==0)
6. {
7. //將私鑰存Session中
8. Session["private_key"] = rsa.ToXmlString(true);
9. }
10. else
11. {
12. bool bLoginSucceed = false;
13. try
14. {
15. string strUserName = Request.Form["txtUserName"];
16. postbackUserName = strUserName;
17. string strPwdToDecrypt = Request.Form["encrypted_pwd"];
18. rsa.FromXmlString((string)Session["private_key"]);
19. byte[] result = rsa.Decrypt(HexStringToBytes(strPwdToDecrypt), false);
20. System.Text.ASCIIEncoding enc = new ASCIIEncoding();
21. string strPwdMD5 = enc.GetString(result);
22. if (string.Compare(strUserName, "user1", true)==0 && string.Compare(strPwdMD5, "14e1b600b1fd579f47433b88e8d85291", true)==0)
23. bLoginSucceed = true;
24. }
25. catch (Exception)
26. {
27.
28. }
29. if (bLoginSucceed)
30. LoginResult = "登錄成功";
31. else
32. LoginResult = "登錄失敗";
33. }
34.
35. //把公鑰適當(dāng)轉(zhuǎn)換,準(zhǔn)備發(fā)往客戶端
36. RSAParameters parameter = rsa.ExportParameters(true);
37. strPublicKeyExponent = BytesToHexString(parameter.Exponent);
38. strPublicKeyModulus = BytesToHexString(parameter.Modulus);
39. }

用戶名“user1”

密碼“123456”

登錄成功!

抓取http報(bào)文看看POST的“密碼”:

這樣的“密碼”的破解就成為了理論上的可行了。