思路如下:
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" οnsubmit="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="验证码" οnkeyup="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 ;
}
}