一般咱們作一個Web應用程序的時候都須要登陸,登陸就要輸入用戶名和登陸密碼,而且,用戶名和登陸密碼都是明文傳輸的,這樣就有可能在中途被別人攔截,尤爲是在網吧等場合。javascript
這裏順帶一個小插曲,我之前有家公司,辦公室裝修時候安排的網口相對較少,不太夠用,因而我和另一個同事使用了一個hub來共享一個網口,這就致使了頗有趣的現象:任何他的網絡包我都能抓獲得,固然了,個人他也能抓獲得。這是否是有很大的安全隱患了?我有可能在不經意間會泄漏本身的密碼。html
因此,不少安全要求較高的網站都不會明文傳輸密碼,它們會使用https來確保傳輸過程的安全,https是用證書來實現的,證書來自於證書頒發機構,固然了,你也能夠本身造一張證書,但這樣別人訪問你的網站的時候仍是會遇到麻煩,由於你本身造的證書不在用戶瀏覽器的信任範圍以內,你還得在用戶瀏覽器上安裝你的證書,來讓用戶瀏覽器相信你的網站,不少用戶並不知道如何操做,就算會操做,也能也不樂意幹;另外一種選擇是你向權威證書頒發機構申請一張證書,但這樣有必定的門檻,還須要付費,也不是咱們樂意乾的事。java
因此,我打算本身實現一個密碼加密傳輸方法。jquery
這裏使用了RSA非對稱加密算法,對稱加密也許你們都已經很熟悉,也就是加密和解密用的都是一樣的密鑰,沒有密鑰,就沒法解密,這是對稱加密。而非對稱加密算法中,加密所用的密鑰和解密所用的密鑰是不相同的:你使用個人公鑰加密,我使用個人私鑰來解密;若是你不使用個人公鑰加密,那我沒法解密;若是我沒有私鑰,我也無法解密。git
我設計的這個登陸密碼加密傳輸方法的原理圖以下:算法
首先,先演練一下非對稱加密:數據庫
static void Main(string[] args) { //用於字符串和byte[]之間的互轉 UTF8Encoding utf8encoder = new UTF8Encoding(); //產生一對公鑰私鑰 RSACryptoServiceProvider rsaKeyGenerator = new RSACryptoServiceProvider(1024); string publickey = rsaKeyGenerator.ToXmlString(false); string privatekey = rsaKeyGenerator.ToXmlString(true); //使用公鑰加密密碼 RSACryptoServiceProvider rsaToEncrypt = new RSACryptoServiceProvider(); rsaToEncrypt.FromXmlString(publickey); string strPassword = "@123#abc$"; Console.WriteLine("The original password is: {0}", strPassword); byte[] byEncrypted = rsaToEncrypt.Encrypt(utf8encoder.GetBytes(strPassword), false); Console.Write("Encoded bytes: "); foreach (Byte b in byEncrypted) { Console.Write("{0}", b.ToString("X")); } Console.Write("\n"); Console.WriteLine("The encrypted code length is: {0}", byEncrypted.Length); //解密 RSACryptoServiceProvider rsaToDecrypt = new RSACryptoServiceProvider(); rsaToDecrypt.FromXmlString(privatekey); byte[] byDecrypted = rsaToDecrypt.Decrypt(byEncrypted, false); string strDecryptedPwd = utf8encoder.GetString(byDecrypted); Console.WriteLine("Decrypted Password is: {0}", strDecryptedPwd); }
你們能夠清楚看到,密碼被加密成128字節長度的密文,爲何是固定128字節呢?這是由於咱們的RSACryptoServiceProvider默認生成的key的長度是1024,即1024位的加密,因此無論你要加密的密碼有多長,它生成的密文的長度確定是128字節,也由於這樣,密碼的長度是有限制的,1024位的RSA算法,只能加密大約100個字節長度的明文,要提升可加密的明文的長度限制,就得增長key的長度,好比把key改到2048位,這樣能加密的明文的長度限制也就變爲大概200出頭這樣……仍是太少啊!並且這樣會帶來加密速度的顯著降低,RSA原本就很慢……是的,比同沒有長度限制的對稱加密,這種非對稱加密的限制可真多,即使是200個字符,又能傳輸什麼東西呢?——密碼!這個就夠了,傳輸完密碼以後,咱們就使用對稱加密,因此,RSA每每是用來「協商」一個對稱加密的key的。瀏覽器
接下去,真正的難點在於用javascript實現一個和.net的RSA兼容的算法。密碼學,對我來講真像天書通常,每次我一看就頭大,這個工做是沒辦法本身作的了,只能到網上找,那是至關的費力啊,找到許多js的RSA實現,但都和.net的這套東西不兼容,最後仍是功夫不負有心人,終於找到了一套。很少說,上代碼:安全
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>RSA Login Test</title> <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script> <script src="Scripts/jQuery.md5.js" type="text/javascript" ></script> <script src="Scripts/BigInt.js" type="text/javascript"></script> <script src="Scripts/RSA.js" type="text/javascript"></script> <script src="Scripts/Barrett.js" type="text/javascript"></script> <script type="text/javascript"> function cmdEncrypt() { setMaxDigits(129); var key = new RSAKeyPair("<%=strPublicKeyExponent%>", "", "<%=strPublicKeyModulus%>"); var pwdMD5Twice = $.md5($.md5($("#txtPassword").attr("value"))); var pwdRtn = encryptedString(key, pwdMD5Twice); $("#encrypted_pwd").attr("value", pwdRtn); $("#formLogin").submit(); return; } </script> </head> <body> <form action="Default.aspx" id="formLogin" method="post"> <div> <div> User Name: </div> <div> <input id="txtUserName" name="txtUserName" value="<%=postbackUserName%>" type="text" maxlength="16" /> </div> <div> Password: </div> <div> <input id="txtPassword" type="password" maxlength="16" /> </div> <div> <input id="btnLogin" type="button" value="Login" onclick="return cmdEncrypt()" /> </div> </div> <div> <input type="hidden" name="encrypted_pwd" id="encrypted_pwd" /> </div> </form> <div> <%=LoginResult%> </div> </body> </html>
這是客戶端代碼,你們能夠看到,基本沒有什麼服務器端代碼,<%=postbackUserName%>用於回顯輸入的用戶名,<%=LoginResult%>用於顯示登陸結果,<%=strPublicKeyExponent%>和<%=strPublicKeyModulus%>則用來告訴客戶端RSA公鑰。須要的javascript文件說明:服務器
對於密碼學,我幾乎一無所知,因此沒辦法跟你們解釋清楚RSA算法的原理,抱歉,我只知道怎麼用。關於javascript中這行代碼:「setMaxDigits(129);」具體表示什麼我也不清楚,我只知道,把參數改成小於129的數以後會致使客戶端的javascript執行進入死循環。服務器端代碼也很簡單:
protected void Page_Load(object sender, EventArgs e) { LoginResult = ""; RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); if (string.Compare(Request.RequestType, "get", true)==0) { //將私鑰存Session中 Session["private_key"] = rsa.ToXmlString(true); } else { bool bLoginSucceed = false; try { string strUserName = Request.Form["txtUserName"]; postbackUserName = strUserName; string strPwdToDecrypt = Request.Form["encrypted_pwd"]; rsa.FromXmlString((string)Session["private_key"]); byte[] result = rsa.Decrypt(HexStringToBytes(strPwdToDecrypt), false); System.Text.ASCIIEncoding enc = new ASCIIEncoding(); string strPwdMD5 = enc.GetString(result); if (string.Compare(strUserName, "user1", true)==0 && string.Compare(strPwdMD5, "14e1b600b1fd579f47433b88e8d85291", true)==0) bLoginSucceed = true; } catch (Exception) { } if (bLoginSucceed) LoginResult = "登陸成功"; else LoginResult = "登陸失敗"; } //把公鑰適當轉換,準備發往客戶端 RSAParameters parameter = rsa.ExportParameters(true); strPublicKeyExponent = BytesToHexString(parameter.Exponent); strPublicKeyModulus = BytesToHexString(parameter.Modulus); }
用戶名「user1」
密碼「123456」
登陸成功!
抓取http報文看看POST的「密碼」:
這樣的「密碼」的破解就成爲了理論上的可行了。:)
下面提供完整代碼下載(使用VS2010開發環境):