思路如下:
1.从进入登录页面时,从服务器取得公钥。
2.在客户端 产生一20位的随机数,并用公钥加密此随机数。
3.用随机数加密密码。
4.将随机数的密文与密码的密文,传送到服务器。
5.服务器此私钥解出随机数,再用随机数解出密码密文。
具体实现:
(1).通过在登录页面通过dwr取得公钥以及需要的js.
<%@page contentType="text/html;charset=GBK"%> <%-- DWR --%> <script type='text/JavaScript' src='dwr/interface/vbaoCommon.js'></script> <script type="text/javascript" src="dwr/engine.js"></script> <script type="text/javascript" src="dwr/util.js"></script> <%-- 验证 --%> <script language=javascript src="js/verify.js"></script> <%-- 软键盘 --%> <link href="css/keyboard.css" rel="stylesheet" type="text/css"> <script language="JavaScript" type="text/JavaScript"> <!-- <%-- 保存全局key变量 --%> var key; var keys; <%-- 进入页面时,请求服务器取得公钥,并生成需要的js --%> vbaoCommon.getJSUrlForLogin("login",handleGetData); <%-- 生成javascript --%> function createJS(jssrc){ var oHead = document.getElementsByTagName('HEAD').item(0); var oScript= document.createElement("script"); oScript.type = "text/javascript"; oScript.src=jssrc; oHead.appendChild(oScript); } <%-- 生成指定长度的随机数 --%> function radomStr(length) { chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; var str = ""; for(x=0;x<length;x++) { i = Math.floor(Math.random() * 62); str += chars.charAt(i); } return str; } function createKey(){ setMaxDigits(130); key =new RSAKeyPair(keys[1],"",keys[0]); } <%-- 处理从服务器返回的数据 --%> function handleGetData(data){ keys = data.split(","); for(var i=2;i<keys.length;i++) { createJS(keys[i]); } setTimeout("createKey()",1000); } <%-- 校验验证码 --%> function autoCheckAuthcode() { var input = $("authcodeInput").value; if(input.length == 4) { vbaoCommon.checkAuthcode(input,callBack); } } <%-- 根据返回值,判断输入验证是否正确 --%> function callBack(boolean) { if(boolean) { $("checkImg").innerHTML='<img src="images/check_right.gif" width="13" height="13"> '; }else { $("checkImg").innerHTML='<img src="images/check_error.gif" width="13" height="13"> '; } } function formLoginSubmit() { //去除用户名首尾的空格 $("userName").value = $("userName").value.Trim(); var userName = $("userName").value; var pwd = $("password").value.Trim(); var authcode = $("authcodeInput").value; ${"password"}.value=Math.random(); var ram = radomStr(20); var en_pwd = window.btoa(VPAY.encrypt(pwd,ram)); $("en_pwd").value = en_pwd; var en_ram = encryptedString(key,ram); $("en_ram").value = en_ram; pwd = ""; en_pwd = ""; en_ram = ""; return true; } //看不清时 更换验证码 function getImageCode() { var randomnum = Math.random(); var getimagecode = $("chkcode"); getimagecode.src = "/vbao/indexCheckImage.do?" + randomnum; autoCheckAuthcode(); } //--> </script> <table width="950" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td width="8" background="images/new_images/lineleft_bk_1.jpg"> </td> <td width="466" align="center" valign="top" bgcolor="#FFFFFF"><img src="images/new_images/1.gif" width="670" height="170"><img src="images/new_images/liucheng.gif" width="670" height="174"></td> <td align="left" valign="top" bgcolor="#FFFFFF"> <table width="100%" height="344" border="0" cellpadding="0" cellspacing="0" bgcolor="#FFFFFF" class="kuang011"> <tr> <td align="center" valign="top" background="images/new_images/login_05.jpg"> <form name="logonForm" action="/vbao/logonVibao.do?method=login" method="post" onsubmit="return formLoginSubmit();""> <table width="100%" border="0" cellpadding="0" cellspacing="0"> <tr> <td height="42" colspan="3" align="right" background="images/new_images/login.jpg" class="font3"> </td> </tr> <tr> <td colspan="3" class="kuang2" align="center"> <font color="red"> <html:errors property="error"/> </font> </td> </tr> <tr> <td width="30%" align="right" height="35" class="font1">登录方式: </td> <td width="70%" align="left" class="font1" colspan="2"> <select name="checkType" class="kuang3" style="width: 135px;"> <option value="1">昵 称</option> <option value="2">卡 号</option> <option value="3">证 件 号</option> </select> </td> </tr> <tr> <td width="30%" height="35" align="right" class="font1">帐 户: </td> <td width="70%" align="left" class="font1" colspan="2"> <input name="userName" id="userName" type="text" class="kuang3" size="25" maxlength="25" style="width: 135px;"></td> </tr> <tr> <td width="30%" height="35" align="right" class="font1">密 码: </td> <td width="70%" align="left" class="font1" colspan="2"> <input name="password" id="password" type="password" size="18" maxlength="12" style="width: 135px;" class="keyboardInput"></td> </tr> <tr> <td width="90%" class="font2" colspan="2" height="45"> 请将下边的文字输入框中</td> <td></td> </tr> <tr> <td colspan="3"> <input name="authcode" alt="验证码" onkeyup="javascript:autoCheckAuthcode();" style="height: 25px;padding: 5px;width: 60px;" maxlength="8" id="authcodeInput" value="" type="text" size="4"/> <img id="chkcode" src="/vbao/indexCheckImage.do" width="50" height="25" style="margin: -5px;"/> <a class="font1" href="javascript:getImageCode();">看不清</a> <em id="checkImg"></em> </td> </tr> <tr> <td height="40" align="right" class="kuang03"> </td> <td align="left" class="kuang03"> </td> </tr> <tr> <td colspan="3" height="50"> <input type="submit" id="loginSubmit" name="submitForm" style="background-image: url('images/zxzx_26.jpg'); cursor:pointer; border:0; width:54; height:24;" value=" "/> <input type="hidden" name="en_pwd" id="en_pwd" value="" /> <input type="hidden" name="en_ram" id="en_ram" value="" /> </td> </tr> </table></form> </td> </tr> </table> </td> <td width="8" background="images/new_images/lineright_bk_1.jpg"> </td> </tr> </table>
(2).在服务端,取得公钥,并取得js地址,回传给客户端
public String getJSUrlForLogin(String sign) { // KeyTool kt = new KeyTool(); RSAPublicKey dhp = null; try { if(pk == null) pk = RSAUtil.getKeyPair().getPublic(); dhp = (RSAPublicKey) pk; } catch (Exception ex) { if (pk == null) try { KeyPair kp = RSAUtil.generateKeyPair(); dhp = (RSAPublicKey)kp.getPublic(); } catch (Exception e) { e.printStackTrace(); } ex.printStackTrace(); } String m_value = dhp.getModulus().toString(16); String e_value = dhp.getPublicExponent().toString(16); return m_value + "," + e_value+",js/keyboard.js,js/RSA.js,js/xxtea.js,js/base64.js,js/BigInt.js,js/Barrett.js"; }
(3).根据地址动态生成javascript
<%-- 生成javascript --%> function createJS(jssrc){ var oHead = document.getElementsByTagName('HEAD').item(0); var oScript= document.createElement("script"); oScript.type = "text/javascript"; oScript.src=jssrc; oHead.appendChild(oScript); } function createKey(){ setMaxDigits(130); key =new RSAKeyPair(keys[1],"",keys[0]); } <%-- 处理从服务器返回的数据 --%> function handleGetData(data){ keys = data.split(","); for(var i=2;i<keys.length;i++) { createJS(keys[i]); } setTimeout("createKey()",1000); }
(4).生成随机串,用此加密密码,发送回服务器
<%-- 生成指定长度的随机数 --%> function radomStr(length) { chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; var str = ""; for(x=0;x<length;x++) { i = Math.floor(Math.random() * 62); str += chars.charAt(i); } return str; } function formLoginSubmit() { //去除用户名首尾的空格 $("userName").value = $("userName").value.Trim(); var userName = $("userName").value; var pwd = $("password").value.Trim(); var authcode = $("authcodeInput").value; ${"password"}.value=Math.random(); var ram = radomStr(20); var en_pwd = window.btoa(VPAY.encrypt(pwd,ram)); $("en_pwd").value = en_pwd; var en_ram = encryptedString(key,ram); $("en_ram").value = en_ram; pwd = ""; en_pwd = ""; en_ram = ""; return true; }
(5).服务端的解密过程
String en_ram = lf.getEn_ram(); byte[] de_pwd = null; try { PrivateKey pk = RSAUtil.getKeyPair().getPrivate(); //用密钥解出随机数,再用随机数解出密文。 byte[] ram_byte = RSAUtil.decrypt(pk, new BigInteger(en_ram,16).toByteArray()); String ram_String = new StringBuffer().append(new String(ram_byte)).reverse().toString(); byte[] temp = Base64.decode(lf.getEn_pwd().getBytes()); de_pwd = XXTEA.decrypt(temp,ram_String.getBytes()); } catch (Exception e) { e.printStackTrace(); } ui.setPASSWORD(new String(de_pwd));
(6).相关工具类
RSAUtil.java
package com.sztelecom.vbao.util; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import javax.crypto.Cipher; /************************************************* Copyright (C), 2008-2009, SunSoft Team Tech. Co., Ltd. File name: RSAUtil.java Description: RSA 工具类。提供加密,解密,生成密钥对等方法。 需要到http://www.bouncycastle.org下载bcprov-jdk14-123.jar。 Others: Function List: History: *************************************************/ /** * Author: sunbeam Version: 1.0 Date: 2008-6-25 */ public class RSAUtil { /** * * 生成密钥对 * * * @return KeyPair * * @throws EncryptException */ public static KeyPair generateKeyPair() throws Exception { try { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); final int KEY_SIZE = 1024;// 没什么好说的了,这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低 keyPairGen.initialize(KEY_SIZE, new SecureRandom()); KeyPair keyPair = keyPairGen.generateKeyPair(); saveKeyPair(keyPair); return keyPair; } catch (Exception e) { throw new Exception(e.getMessage()); } } public static KeyPair getKeyPair() throws Exception { FileInputStream fis = new FileInputStream("C:/RSAKey.txt"); ObjectInputStream oos = new ObjectInputStream(fis); KeyPair kp = (KeyPair) oos.readObject(); oos.close(); fis.close(); return kp; } public static void saveKeyPair(KeyPair kp) throws Exception { FileOutputStream fos = new FileOutputStream("C:/RSAKey.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); // 生成密钥 oos.writeObject(kp); oos.close(); fos.close(); } /** * * 生成公钥 * * * @param modulus * * @param publicExponent * * @return RSAPublicKey * * @throws Exception */ public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws Exception { KeyFactory keyFac = null; try { keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); } catch (NoSuchAlgorithmException ex) { throw new Exception(ex.getMessage()); } RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger( modulus), new BigInteger(publicExponent)); try { return (RSAPublicKey) keyFac.generatePublic(pubKeySpec); } catch (InvalidKeySpecException ex) { throw new Exception(ex.getMessage()); } } /** * * 生成私钥 * * * @param modulus * * @param privateExponent * * @return RSAPrivateKey * * @throws Exception */ public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception { KeyFactory keyFac = null; try { keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); } catch (NoSuchAlgorithmException ex) { throw new Exception(ex.getMessage()); } RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger( modulus), new BigInteger(privateExponent)); try { return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec); } catch (InvalidKeySpecException ex) { throw new Exception(ex.getMessage()); } } /** * * 加密 * * * @param key * 加密的密钥 * * @param data * 待加密的明文数据 * * @return 加密后的数据 * * @throws Exception */ public static byte[] encrypt(PublicKey pk, byte[] data) throws Exception { try { Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); cipher.init(Cipher.ENCRYPT_MODE, pk); int blockSize = cipher.getBlockSize();// 获得加密块大小,如:加密前数据为128个byte,而key_size=1024 // 加密块大小为127 // byte,加密后为128个byte;因此共有2个加密块,第一个127 // byte第二个为1个byte int outputSize = cipher.getOutputSize(data.length);// 获得加密块加密后块大小 int leavedSize = data.length % blockSize; int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize; byte[] raw = new byte[outputSize * blocksSize]; int i = 0; while (data.length - i * blockSize > 0) { if (data.length - i * blockSize > blockSize) cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize); else cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize); // 这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到 // ByteArrayOutputStream中,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了 // OutputSize所以只好用dofinal方法。 i++; } return raw; } catch (Exception e) { throw new Exception(e.getMessage()); } } /** * * 解密 * * * @param key * 解密的密钥 * * @param raw * 已经加密的数据 * * @return 解密后的明文 * * @throws Exception */ public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception { try { Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); cipher.init(cipher.DECRYPT_MODE, pk); int blockSize = cipher.getBlockSize(); ByteArrayOutputStream bout = new ByteArrayOutputStream(64); int j = 0; while (raw.length - j * blockSize > 0) { bout.write(cipher.doFinal(raw, j * blockSize, blockSize)); j++; } return bout.toByteArray(); } catch (Exception e) { throw new Exception(e.getMessage()); } } }
XXTEA.java
/************************************************* Copyright (C), 2008-2009, SunSoft Team Tech. Co., Ltd. File name: XXTEA.java Description: Others: Function List: History: *************************************************/ package com.sztelecom.vbao.util; import java.nio.charset.Charset; /** * Author: sunbeam * Version: 1.0 * Date: 2008-6-24 */ public class XXTEA { /** * Encrypt data with key. * * @param data * @param key * @return */ public static byte [] encrypt ( byte [] data , byte [] key ) { if ( data . length == 0 ) { return data ; } return toByteArray ( encrypt ( toIntArray ( data , true ) , toIntArray ( key , false )) , false ) ; } /** * Decrypt data with key. * * @param data * @param key * @return */ public static byte [] decrypt ( byte [] data , byte [] key ) { if ( data . length == 0 ) { return data ; } return toByteArray ( decrypt ( toIntArray ( data , false ) , toIntArray ( key , false )) , true ) ; } /** * Encrypt data with key. * * @param v * @param k * @return */ public static int [] encrypt ( int [] v , int [] k ) { int n = v . length - 1 ; if ( n < 1 ) { return v ; } if ( k . length < 4 ) { int [] key = new int [ 4 ] ; System . arraycopy ( k , 0 , key , 0 , k . length ) ; k = key ; } int z = v [ n ] , y = v [ 0 ] , delta = 0x9E3779B9 , sum = 0 , e ; int p , q = 6 + 52 / ( n + 1 ) ; while ( q -- > 0 ) { sum = sum + delta ; e = sum >>> 2 & 3 ; for ( p = 0 ; p < n ; p ++ ) { y = v [ p + 1 ] ; z = v [ p ] += ( z >>> 5 ^ y << 2 ) + ( y >>> 3 ^ z << 4 ) ^ ( sum ^ y ) + ( k [ p & 3 ^ e ] ^ z ) ; } y = v [ 0 ] ; z = v [ n ] += ( z >>> 5 ^ y << 2 ) + ( y >>> 3 ^ z << 4 ) ^ ( sum ^ y ) + ( k [ p & 3 ^ e ] ^ z ) ; } return v ; } /** * Decrypt data with key. * * @param v * @param k * @return */ public static int [] decrypt ( int [] v , int [] k ) { int n = v . length - 1 ; if ( n < 1 ) { return v ; } if ( k . length < 4 ) { int [] key = new int [ 4 ] ; System . arraycopy ( k , 0 , key , 0 , k . length ) ; k = key ; } int z = v [ n ] , y = v [ 0 ] , delta = 0x9E3779B9 , sum , e ; int p , q = 6 + 52 / ( n + 1 ) ; sum = q * delta ; while ( sum != 0 ) { e = sum >>> 2 & 3 ; for ( p = n ; p > 0 ; p -- ) { z = v [ p - 1 ] ; y = v [ p ] -= ( z >>> 5 ^ y << 2 ) + ( y >>> 3 ^ z << 4 ) ^ ( sum ^ y ) + ( k [ p & 3 ^ e ] ^ z ) ; } z = v [ n ] ; y = v [ 0 ] -= ( z >>> 5 ^ y << 2 ) + ( y >>> 3 ^ z << 4 ) ^ ( sum ^ y ) + ( k [ p & 3 ^ e ] ^ z ) ; sum = sum - delta ; } return v ; } /** * Convert byte array to int array. * * @param data * @param includeLength * @return */ private static int [] toIntArray ( byte [] data , boolean includeLength ) { int n = ((( data . length & 3 ) == 0 ) ? ( data . length >>> 2 ) : (( data . length >>> 2 ) + 1 )) ; int [] result ; if ( includeLength ) { result = new int [ n + 1 ] ; result [ n ] = data . length ; } else { result = new int [ n ] ; } n = data . length ; for ( int i = 0 ; i < n ; i ++ ) { result [ i >>> 2 ] |= ( 0x000000ff & data [ i ]) << (( i & 3 ) << 3 ) ; } return result ; } /** * Convert int array to byte array. * * @param data * @param includeLength * @return */ private static byte [] toByteArray ( int [] data , boolean includeLength ) { int n ; if ( includeLength ) { n = data [ data . length - 1 ] ; } else { n = data . length << 2 ; } byte [] result = new byte [ n ] ; for ( int i = 0 ; i < n ; i ++ ) { result [ i ] = ( byte ) ( data [ i >>> 2 ] >>> (( i & 3 ) << 3 )) ; } return result ; } }