一般我们在form提交时,可能会担心我们的参数显示给用户看。
我们主要解决的是以get方式进行url参数传递的问题,如:
http://xxx.xxx.xxx.xxx/xxx/xxx.do?pId=101&imageName=processimage.jpg这样的urlpId=101&imageName=processimage.jgp这样的参数如何进行加密。
我们采用:
java.net.URLEncoder.encode(Base64编码(加密字串),StringCode) 这样的方法来对url中的参数进行加密。
首先我们先说一下如何加密。
对于url中参数进行加密的过程,我不建议使用RSA或者是三重DES这样的加密算法,主要原因在于性能和速度会受影响。
我建议大家使用对称加密如:DES或者是PBE算法。
我们在这边就使用PBEWithMD5AndDES来实现加密。
对于一个纯文本,加密后它会变成一堆乱码,这堆乱码包括了许多非法字符,我们不希望把这些字符放入bean中,因此在加密完后,我们还要对加密结果进行base64编码。
PBE从字面上理解,它必须使用一个口令,我们不希望我们的加密过于复杂而影响页面跳转的速度,因此我们不采用口令+KEY的形式,我们这边的口令就是我们的KEY。
因此,我们的整个加密过程实现如下:
输入口令(KEY)--> 加密文本 --> 以base64对加密后的结果进行编码-->以java.net.URLEncoder.encode编码成浏览器可以识别的形式-->传输给接受的action。
而解密过程如下:
接受的action得到参数-->以base64对结果进行解码-->得到纯加密文本-->解密-->得到解密后的值。
这边对于BASE64的原理不多说了,只说实现,目前网上有很多实现方式,有自己写的,有用sun.misc.*的,我们在这个例子里将使用javax.mail.internet.MimeUtility自带的base64编码工具。
需要引入activation.jar和mail.jar两个包。下面是具体的实现:
package com.xmobo.merchantAPI.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.mail.internet.MimeUtility;
public class Base64 {
public static byte[] encode(byte[] b) throws Exception {
ByteArrayOutputStream baos = null;
OutputStream b64os = null;
try {
baos = new ByteArrayOutputStream();
b64os = MimeUtility.encode(baos, "base64");
b64os.write(b);
b64os.close();
return baos.toByteArray();
} catch (Exception e) {
throw new Exception(e);
} finally {
try {
if (baos != null) {
baos.close();
baos = null;
}
} catch (Exception e) {
}
try {
if (b64os != null) {
b64os.close();
b64os = null;
}
} catch (Exception e) {
}
}
}
public static byte[] decode(byte[] b) throws Exception {
ByteArrayInputStream bais = null;
InputStream b64is = null;
try {
bais = new ByteArrayInputStream(b);
b64is = MimeUtility.decode(bais, "base64");
byte[] tmp = new byte[b.length];
int n = b64is.read(tmp);
byte[] res = new byte[n];
System.arraycopy(tmp, 0, res, 0, n);
return res;
} catch (Exception e) {
throw new Exception(e);
} finally {
try {
if (bais != null) {
bais.close();
bais = null;
}
} catch (Exception e) {
}
try {
if (b64is != null) {
b64is.close();
b64is = null;
}
} catch (Exception e) {
}
}
}
}
有了BASE64的工具类,下面的工作将变得简单了,编写我们的加密解密工具类吧:
package com.xmobo.merchantAPI.utils;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class SecurityHelper {
protected final static Log logger = LogFactory.getLog(SecurityHelper.class);
private final static int ITERATIONS = 20;
public static String encrypt(String key, String plainText) throws Exception {
// String encryptTxt = "";
try {
byte[] salt = new byte[8];
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(key.getBytes());
byte[] digest = md.digest();
for (int i = 0; i < 8; i++) {
salt[i] = digest[i];
}
PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey skey = keyFactory.generateSecret(pbeKeySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, skey, paramSpec);
byte[] cipherText = cipher.doFinal(plainText.getBytes());
String saltString = new String(Base64.encode(salt));
String ciphertextString = new String(Base64.encode(cipherText));
return saltString + ciphertextString;
} catch (Exception e) {
throw new Exception("Encrypt Text Error:" + e.getMessage(), e);
}
}
public static String decrypt(String key, String encryptTxt) throws Exception {
int saltLength = 12;
try {
String salt = encryptTxt.substring(0, saltLength);
String ciphertext = encryptTxt.substring(saltLength, encryptTxt.length());
byte[] saltarray = Base64.decode(salt.getBytes());
byte[] ciphertextArray = Base64.decode(ciphertext.getBytes());
PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey skey = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(saltarray, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.DECRYPT_MODE, skey, paramSpec);
byte[] plaintextArray = cipher.doFinal(ciphertextArray);
return new String(plaintextArray);
} catch (Exception e) {
throw new Exception(e);
}
}
}
注意上面加粗的三处地方:
private final static int ITERATIONS = 20;
上面的值越大,加密越深,一般例子都以"Java安全性编程指南”这本书中的例子的值为准,设成1000,我们在这边只需要20就够了,原因就是考虑到加解密的速度问题。
int saltLength = 12;
这是base64解码后源的长度,加密后再经BASE64编码后源的长度为8,BASE64解码后源的长度为12,至于为什么,这也是根据BASE64的原理得出的,具体可以看BASE64原理,网上很多,说得也都很简单。
PBEWithMD5AndDES
我们使用的是PBEWithMD5AndDES加密。
下面编写一个测试类
public static void main(String[] args) {
String encryptTxt = "";
String plainTxt = "hello oh my god";
try {
System.out.println(plainTxt);
encryptTxt = encrypt("mypassword01", plainTxt);
plainTxt = decrypt("mypassword01", encryptTxt);
System.out.println(encryptTxt);
System.out.println(plainTxt);
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
MyTaskDTO taskDTO = new MyTaskDTO();
TaskInstance ti = (TaskInstance) it.next();
taskDTO.setTaskName(ti.getName());
taskDTO.setTaskCreateDate(sd.format(ti.getCreate()));
taskDTO.setTaskDescr(ti.getDescription());
/* no encrypted data */
String taskId = String.valueOf(ti.getId());
String tokenId = String.valueOf(ti.getToken().getId());
processImgName = PropertyUtil.getProperty(
Constants.BPM_PROCESS_PAYMENT_PROCESSIMAGE).toString()
+ ".jpg";
processDefId = String.valueOf(ti.getToken()
.getProcessInstance().getProcessDefinition().getId());
/* encrypted data */
taskId = EncryptUrlPara.encrypt(taskId);
tokenId = EncryptUrlPara.encrypt(tokenId);
processImgName = EncryptUrlPara.encrypt(processImgName);
processDefId = EncryptUrlPara.encrypt(processDefId);
taskDTO.setTaskId(taskId);
taskDTO.setTokenId(tokenId);
taskDTO.setProcessDefinitionId(processDefId);
taskDTO.setProcessImageName(processImgName);
把上述这个bean放入request中,带到下一个jsp页面中后,在jsp页面的处理如下:
String processImgPath=taskDTO.getProcessImageName();
String processDefId=taskDTO.getProcessDefinitionId();
processImgPath=java.net.URLEncoder.encode(processImgPath,"UTF-8");
processDefId=java.net.URLEncoder.encode(processDefId,"UTF-8");
String showProcessImgUrl=request.getContextPath()+"/queryMyTask.do";
查看当前进程
我们假设我们的接受的action为: queryMyTask.do,它接受一系列的参数,基中,processDefId和processImgPath是加密的。
实现如下:
String processImgFilePath = "";
String processDefinitionId = (String)request.getParameter("processDefinitionId");
processImgFilePath = (String)request.getParameter("processImgPath");
processDefinitionId =EncryptUrlPara.decrypt(processDefinitionId);
processImgFilePath =EncryptUrlPara.decrypt(processImgFilePath);
需要注意的是此处不需要再decode了。
因为我们这边的key就是口令,是一个文本,我们将它存放在server端的properties中,当然,我们也是加密存放的。
我们使用spring+jasypt1.5(java simple encrypt包)。
设我们有一个properties文件,其中:
security.des.key=ENC(OlO0LqELUuLOVreCtDngHaNgMcZWUyUg)
这个就是我们在encrypt和decrypt方法中用到的key。
我们不希望这个key以明文的形式设在properties中,我们对这个key再进行一次加密用的同样也是PBEWithMD5AndDES,当然因为有了spring因为有了jasypt包,因此这个过程一切是自动的。
我们使用jasypt包下的bin中自带的encrypt.bat工具:
encrypt input=mykey password=secret algorithm=PBEWithMD5AndDES
该命令会输出一行乱码,把这行乱码复制到properties文件中,在外层加上ENC(),如:
生成: OlO0LqELUuLOVreCtDngHaNgMcZWUyUg
放入properties后需要转换成: ENC(OlO0LqELUuLOVreCtDngHaNgMcZWUyUg)
然后在工程部署的机器上需要设一个环境变理,如:
set APP_ENCRYPTION_PASSWORD=secret 此处的值必须和上面encrypt.bat命令行中的password=后的值一样。
(Linux请用export APP_ENCRYPTION_PASSWORD=secret)
然后配置spring,使该properties在工程被app 容器load时,自动解密,这样我们在我们的方法中直接取到该KEY时就已经是明文了(解密过程是jasypt+spring自动完成的),以下是这一步配置的详细内容:
classpath:xxx.properties
Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/mail/util/LineInputStream
如果你用myEclipse进行开发的话,运行时可能会出现以下的错误:
Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/mail/util/LineInputStream
原因是jar包版本不统一,解决方法如下:
删除Java EE 5 Libraries/javaee.jar/mail里的包有东西.
具体方法如下:
用rar打开D:\MyEclipse8.6\Genuitec\Common\plugins\com.genuitec.eclipse.j2eedt.core_8.6.0.me201007292038\data\libraryset\EE_5\javaee.jar
,然后删除mail,一切就ok了。