解决这一问题最佳方案当然是使用非对称加密。简单的说,非对称加密算法需要两个密钥,分别称为公钥和私钥,其中公钥会被公布出来,而私钥由个了保管(就像保管自己的密码一样)。使用公钥加密的数据是不能用公钥解密的,只能由私钥来解密。如果将私钥保存在服务器,把公钥发送给浏览器对密码原文进行加密,那么加密后的数据在传输过程中是安全的,因为私钥始终不会出现在传输过程,这个加密数据就不能轻松的解开。关于非对称加密的知识,学霸们请去各种百科上搜索,这里就不多说了。
下面就以C#和JavaScript为例说明一下加密传输密码和后台解密的过程。
当然第一步是要产生公钥和私钥,自己用C#写个小小的控制台程序或者Windows程序就能解决这个问题,顺便熟悉一下C#的RSA。顺便说一下,维基百科提到,要保证安全至少得使用1024位的Key,.NET的RSA支持384到2048位的Key,这里就以1024位为例吧,下面的程序会在执行目录输出一个key.xml文件,保存了产生的公钥和私钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code>[STAThread]</code>
<code>static</code> <code>void</code> <code>Main(</code><code>string</code><code>[] args)</code>
<code>{</code>
<code> </code><code>string</code> <code>keyFileName</code>
<code> </code><code>= Path.Combine(AppDomain.CurrentDomain.BaseDirectory,</code>
<code> </code><code>"key.xml"</code><code>;</code>
<code> </code><code>const</code> <code>int</code> <code>keySize = 1024;</code>
<code> </code><code>// 这个类在System.Security.Cryptography命名空间中</code>
<code> </code><code>RSACryptoServiceProvider sp = </code><code>new</code> <code>RSACryptoServiceProvider(keySize);</code>
<code> </code><code>// 参数true表示XML中包含私钥。如果给false表示只生成公钥的XML</code>
<code> </code><code>string</code> <code>str = sp.ToXmlString(</code><code>true</code><code>);</code>
<code> </code><code>using</code> <code>(TextWriter writer = </code><code>new</code> <code>StreamWriter(keyFileName)) {</code>
<code> </code><code>writer.Write(str);</code>
<code> </code><code>}</code>
<code>}</code>
打开生成的key.xml可以看到,这个Key值包含了如下几个部分:Modulus、Exponent、P、Q、DP、DQ、InverseQ、D。这几个部分的值都是以Base64编码保存的。其中,Modulus和Exponent就是组成公钥的部分,也就是需要传递给浏览器,简称M和E,用于加密的两个数值。
生成key.xml之后,有两种方式使用,一种是在使用时从XML文件导入;另一种方式是把这几个值提取出来写在某个类中。导入XML需要先将XML文本读取到一个string中,再用RSACryptoServiceProvider的实例方法FromXmlString(string)导入。因为需要向浏览器提供M和E,所以还要把这两个值提取出来
<code>RSACryptoServiceProvider rsa = </code><code>new</code> <code>RSACryptoServiceProvider();</code>
<code>rsa.FromXmlString(xmlString);</code>
<code>RSAParameters rsap = rsa.ExportParameters(</code><code>false</code><code>);</code>
<code>//下面就是需要的两个值</code>
<code>//rsap.Exponent</code>
<code>//rsap.Modulus</code>
如果嫌每次载入费事,可以这样(其中所有字符串常量都是从key.xml中拷贝过来的)
<code>private</code> <code>static</code> <code>readonly</code> <code>RSAParameters rsap = </code><code>new</code> <code>RSAParameters</code>
<code> </code><code>Modulus = Convert.FromBase64String(</code><code>@"uQlRZvfH6MMdhNRgiAlKMY88dqsU2suKNIWbHY/FiTsvDgH5DLmNmGMp85qtQwSPhBQ+/E7DQkvk1OxIN7EBL+21NRPJIaDKuJciWC940ZFVU0d5oUujKy5uCrF/rfZce8MXjoiErtc+QRjCKI8wfGdIKuclooEPiJwb1rydMuE="</code><code>),</code>
<code> </code><code>Exponent = Convert.FromBase64String(</code><code>@"AQAB"</code><code>),</code>
<code> </code><code>P = Convert.FromBase64String(</code><code>@"wSwI9i+aM6h7hayvFD01iINAeZ9JK5qExBJAWDzjOQwWRE9x1dCX52jb+HrutwblfqQuOk6hazOmGTluxITXQw=="</code><code>),</code>
<code> </code><code>Q = Convert.FromBase64String(</code><code>@"9TfkbPTexGpQ9ZHNjYnmRJLcG8wG6yzzJ/RrWIjq1IKQYMhYDq08bNbUVuXlntKW9GgmEYnuhP8smrH5y+mRCw=="</code><code>),</code>
<code> </code><code>DP = Convert.FromBase64String(</code><code>@"s2Xx7LDIxLD0BnEZJ/KwhNdgSZNkoNof8vgASfJCE/jltQsS7T+L053OrDV+/PuqprJTPFNKFgUhfMuZ02iLgQ=="</code><code>),</code>
<code> </code><code>DQ = Convert.FromBase64String(</code><code>@"5IfLXXO0LI78lm/khlUPAbdwZIN3qzMABat3Y1Jur9BiZ6Au2LbASprH15h3r9WJE4wAdnX6kX4SfrUBHPW20w=="</code><code>),</code>
<code> </code><code>InverseQ = Convert.FromBase64String(</code><code>@"FhlNb2WkipUaXvuwDxEWPeE754+qM2F5otEUP9clG91yaerdsBpBmU0G6S2AqUNjr/qgfpQyl1EW2dl10rmTpw=="</code><code>),</code>
<code> </code><code>D = Convert.FromBase64String(</code><code>@"WjhPXv/Qks7T7UiqGppA+UIoToojPH1C0VIVrEfGHp/jVRakKs6sWhF7yoHwGf22xkUi4t26efBMTn84xSLCexjQwj5AQtYk+3Qr2QjRDdn2ooIV1gWKW/C0O0+80Y6PEeszItuBVfjKC6mNEcZ1g44/wOdvIG7Olsl0F7vmQrM="</code><code>)</code>
<code>};</code>
将E和M传递给浏览器的方式,最直接就是写在HTML里,我个人比较喜欢把一些小数据写在<HEAD>标签的属性中,就像这样:<HEAD M="..." E="...">。C#代码也很简单:
<code>Header.Attributes[</code><code>"M"</code><code>] = rsap.Modulus.HexEncode();</code>
<code>Header.Attributes[</code><code>"E"</code><code>] = rsap.Exponent.HexEncode();</code>
这里用到了一个byte[]的扩展方法HexEncode,即将byte[]转换为16进制字符串的方法,它最简单(但不一定是最快)的实现方法是
<code>public</code> <code>static</code> <code>string</code> <code>HexEncode(</code><code>this</code> <code>byte</code><code>[] me)</code>
<code> </code><code>return</code> <code>BitConverter.ToString(me).Replace(</code><code>"-"</code><code>, </code><code>string</code><code>.Empty);</code>
现在是浏览器端的加密过程,引入需要的JS文件先:
<code><</code><code>script</code> <code>type</code><code>=</code><code>"text/javascript"</code> <code>src</code><code>=</code><code>"js/BigInt.js"</code><code>></</code><code>script</code><code>></code>
<code><</code><code>script</code> <code>type</code><code>=</code><code>"text/javascript"</code> <code>src</code><code>=</code><code>"js/Barrett.js"</code><code>></</code><code>script</code><code>></code>
<code><</code><code>script</code> <code>type</code><code>=</code><code>"text/javascript"</code> <code>src</code><code>=</code><code>"js/RSA.js"</code><code>></</code><code>script</code><code>></code>
<code><</code><code>script</code> <code>type</code><code>=</code><code>"text/javascript"</code> <code>src</code><code>=</code><code>"js/jquery-2.1.0.js"</code><code>></</code><code>script</code><code>></code>
然后在加密密码之前当然要先得到RSAKeyPair对象,这个对象的构造函数定义在RSA.js中。不过在new RSAKeyPair之前,必须先调用setMaxDigits()函数,原因在BigInt.js中有说明,setMaxDigits()的参数值根据选用的RSA的Key大小不同,如果计算我不太清楚,不过按ohdave.com的示例(下载页面的源码就是示例),1024位的Key,应该设置setMaxDigits(130);如果是2048位的则应该设置为260。所以产生RSAKeyPair对象的代码应该是这样:
<code>$(</code><code>function</code><code>() {</code>
<code> </code><code>var</code> <code>key = (</code><code>function</code><code>() {</code>
<code> </code><code>var</code> <code>m = $(</code><code>"head"</code><code>).attr(</code><code>"M"</code><code>);</code>
<code> </code><code>var</code> <code>e = $(</code><code>"head"</code><code>).attr(</code><code>"E"</code><code>);</code>
<code> </code><code>setMaxDigits(130);</code>
<code> </code><code>// 第一个参数是加密因子,第二个参数是解密因子</code>
<code> </code><code>// 因为浏览器端不需要解密,所以第二个参数传入空字符串</code>
<code> </code><code>return</code> <code>new</code> <code>RSAKeyPair(e, </code><code>""</code><code>, m);</code>
<code> </code><code>})();</code>
<code>})</code>
在提交数据之前对密码进行加密
<code>var</code> <code>encryptedPass = encryptedString(key, $(</code><code>"#password"</code><code>).val());</code>
提交到后台之后,C#解密的过程
<code>string</code> <code>hex = Request[</code><code>"encrypted_pass"</code><code>]</code>
<code>byte</code><code>[] data = hex.HexDecode();</code>
<code>// 前面定义的private static readonly RSAParameter rsap = ...</code>
<code>rsa.ImportParameters(rsap);</code>
<code>byte</code><code>[] source = rsa.Decrypt(data, </code><code>false</code><code>);</code>
<code>string</code> <code>password = Encodnig.ASCII.GetString(source);</code>
密码原文得到,剩下的事情就好说了,当然是保存密码,记得先使用HMAC算法加密哦。后面有示例下载,不是很明显,仔细找找。
本文转自边城__ 51CTO博客,原文链接:http://blog.51cto.com/jamesfancy/1361925,如需转载请自行联系原作者