Java 前端加密传输后端解密以及验证码功能
1. 加密解密
1.1 前端js加密概述
对系统安全性要求比较高,那么需要选择https协议来传输数据。当然很多情况下一般的web网站,如果安全要求不是很高的话,用http协议就可以了。在这种情况下,密码的明文传输显然是不合适的,因为如果请求在传输过程中被截了,就可以直接拿明文密码登录网站了。
HTTPS(443)在HTTP(80)的基础上加入了SSL(Secure Sockets Layer 安全套接层)协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。传输前用公钥加密,服务器端用私钥解密。
对于使用http协议的web前端的加密,只能防君子不能防小人。前端是完全暴露的,包括你的加密算法。
知道了加密算法,密码都是可以破解的,只是时间问题。请看知乎上的一篇文章:对抗拖库
所以加密是为了增加破解的时间成本,如果破解需要花费的时间让人难以接受,这也就达到了目的。
而为了保证数据库中存储的密码更安全,则需要在后端用多种单向(非对称)加密手段混合进行加密存储。
前端加密后端又需要解密,所以需要对称加密算法,即前端使用 encrypted = encrypt(password+key),后端使用 password = decrypt(encrypted +key) ,前端只传输密码与key加密后的字符串encrypted ,这样即使请求被拦截了,也知道了加密算法,但是由于缺少key所以很难破解出明文密码。所以这个key很关键。而这个key是由后端控制生成与销毁的,用完即失效,所以即使可以模拟用加密后的密码来发请求模拟登录,但是key已经失效了,后端还是验证不过的。
注意,如果本地环境本就是不安全的,key被知道了,那就瞬间就可以用解密算法破解出密码了。这里只是假设传输的过程中被截获的情形。所以前端加密是防不了小人的。如果真要防,可以将加密算法的js文件进行压缩加密,不断更新的手段来使js文件难以获取,让黑客难以获取加密算法。变态的google就是这么干的,自己实现一个js虚拟机,通过不断更新加密混淆js文件让加密算法难以获取。这样黑客不知道加密算法就无法破解了。
常用的对称加密算法有DES、3DES(TripleDES)、AES、RC2、RC4、RC5和Blowfis。可以参考:常用加密算法的Java实现总结
这里采用js端与java端互通的AES加密算法。
1.2 前后端加密解密
1.2.1 引用的js加密库
<script src="${request.contextPath}/resources/plugins/jQuery/jquery-2.2.4.min.js"></script>
<script src="${request.contextPath}/resources/cryptojs/aes.js"></script>
<script src="${request.contextPath}/resources/cryptojs/mode-ecb-min.js"></script>
- 1
- 2
- 3
1.2.2 js加密解密
var data = "888888";
var srcs = CryptoJS.enc.Utf8.parse(data);
var key = CryptoJS.enc.Utf8.parse('o7H8uIM2O5qv65l2');//Latin1 w8m31+Yy/Nw6thPsMpO5fg==
function Encrypt(word){
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
return encrypted.toString();
}
function Decrypt(word){
var decrypt = CryptoJS.AES.decrypt(word, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这里key是页面加载的时候由服务器端生成的,用隐藏域保存。
1.2.3 Java端加密解密(PKCS5Padding与js的Pkcs7一致)
package com.jykj.demo.util;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
public class EncryptUtil {
private static final String KEY = "abcdefgabcdefg12";
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
public static String base64Encode(byte[] bytes){
return Base64.encodeBase64String(bytes);
}
public static byte[] base64Decode(String base64Code) throws Exception{
return new BASE64Decoder().decodeBuffer(base64Code);
}
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
return cipher.doFinal(content.getBytes("utf-8"));
}
public static String aesEncrypt(String content, String encryptKey) throws Exception {
return base64Encode(aesEncryptToBytes(content, encryptKey));
}
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
return aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
}
/**
* 测试
*
*/
public static void main(String[] args) throws Exception {
String content = "Test String么么哒"; //0gqIDaFNAAmwvv3tKsFOFf9P9m/6MWlmtB8SspgxqpWKYnELb/lXkyXm7P4sMf3e
System.out.println("加密前:" + content);
System.out.println("加密密钥和解密密钥:" + KEY);
String encrypt = aesEncrypt(content, KEY);
System.out.println(encrypt.length()+":加密后:" + encrypt);
String decrypt = aesDecrypt(encrypt, KEY);
System.out.println("解密后:" + decrypt);
}
}
2. 验证码
2.1 概述
验证码是用来区分人机的操作。
验证码划代的标准是人机识别过程中基于对人类知识的应用。
第一代:标准验证码
这一代验证码是即是我们常见的图形验证码、语音验证码,基于机器难以处理复杂的计算机视觉及语音识别问题,而人类却可以轻松的识别来区分人类及机器。这一代验证码初步利用了人类知识容易解答,而计算机难以解答的机制进行人机判断。
第二代:创新验证码
第二代验证码是基于第一代验证码的核心思想(通过人类知识可以解答,而计算机难以解答的问题进行人机判断)而产生的创新的交互优化型验证码。第二代验证码基于第一代验证码的核心原理--“人机之间知识的差异”,拓展出大量创新型验证码。
第三代:无知识型验证码
第三代验证码最大的特点是不再基于知识进行人机判断,而是基于人类固有的生物特征以及操作的环境信息综合决策,来判断是人类还是机器。无知识型验证码最大特点即无需人类思考,从而不会打断用户操作,进而提供更好的用户体验。
如Google的新版ReCaptcha、阿里巴巴的滑动验证。参考知乎 关于验证码
2.2 验证码生成器
package com.jykj.demo.util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
/**
* 验证码生成器
*
*/
public class ValidateCode {
// 图片的宽度。
private int width = 160;
// 图片的高度。
private int height = 28;
// 验证码字符个数
private int codeCount = 4;
// 验证码干扰线数
private int lineCount = 150;
// 验证码
private String code = null;
// 验证码图片Buffer
private BufferedImage buffImg = null;
private char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
public ValidateCode() {
this.createCode();
}
/**
*
* @param width
* 图片宽
* @param height
* 图片高
*/
public ValidateCode(int width, int height) {
this.width = width;
this.height = height;
this.createCode();
}
/**
*
* @param width
* 图片宽
* @param height
* 图片高
* @param codeCount
* 字符个数
* @param lineCount
* 干扰线条数
*/
public ValidateCode(int width, int height, int codeCount, int lineCount) {
this.width = width;
this.height = height;
this.codeCount = codeCount;
this.lineCount = lineCount;
this.createCode();
}
public void createCode() {
int x = 0, fontHeight = 0, codeY = 0;
int red = 0, green = 0, blue = 0;
x = width / (codeCount + 2);// 每个字符的宽度
fontHeight = height - 2;// 字体的高度
codeY = height - 4;
// 图像buffer
buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffImg.createGraphics();
// 生成随机数
Random random = new Random();
// 将图像填充为白色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// 创建字体
Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
g.setFont(font);
//干扰线
for (int i = 0; i < lineCount; i++) {
int xs = random.nextInt(width);
int ys = random.nextInt(height);
int xe = xs + random.nextInt(width / 8);
int ye = ys + random.nextInt(height / 8);
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
g.setColor(new Color(red, green, blue));
g.drawLine(xs, ys, xe, ye);
}
// randomCode记录随机产生的验证码
StringBuffer randomCode = new StringBuffer();
// 随机产生codeCount个字符的验证码。
for (int i = 0; i < codeCount; i++) {
String strRand = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
// 产生随机的颜色值,让输出的每个字符的颜色值都将不同。
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
g.setColor(new Color(red, green, blue));
g.drawString(strRand, (i + 1) * x, codeY);
// 将产生的四个随机数组合在一起。
randomCode.append(strRand);
}
// 将四位数字的验证码保存到Session中。
code = randomCode.toString();
}
public void write(String path) throws IOException {
OutputStream sos = new FileOutputStream(path);
this.write(sos);
}
public void write(OutputStream sos) throws IOException {
ImageIO.write(buffImg, "png", sos);
sos.close();
}
public BufferedImage getBuffImg() {
return buffImg;
}
public String getCode() {
return code;
}
}
2.3 控制器使用验证码 如 CodeController
@RequestMapping("/getCode.do")
public void getCode(HttpServletRequest reqeust, HttpServletResponse response) throws IOException {
response.setContentType("image/jpeg");
// 禁止图像缓存。
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
HttpSession session = reqeust.getSession();
ValidateCode vCode = new ValidateCode(100, 28, 4, 100);
session.setAttribute(Helper.SESSION_CHECKCODE, vCode.getCode());
vCode.write(response.getOutputStream());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
3. 应用
实现功能:前端AES加密传输后端解密以及n次输入验证不通过后需要验证码
有了上面的基础,实现起来应该不难了。
3.1 login.html
<input type="hidden" id="KEY" value="${Session.login_token}"/>
<form action="signIn" method="post" id="loginForm">
<div class="form-group has-feedback">
<input type="text" class="form-control" placeholder="Email" name="username"
onkeydown="javascript:if(event.keyCode==13) $('#password').focus();">
</div>
<div class="form-group has-feedback">
<input type="password" class="form-control" id="password"
id="formPwd" onkeydown="javascript:if(event.keyCode==13) login();" >
</div>
<#if Session.login_failure_count?? && (Session.login_failure_count <=0) >
<div class="form-group has-feedback">
<input name="checkCode" onkeydown="javascript:if(event.keyCode==13) login();" type="text" id="checkCode" maxlength="4" onblur="checkTheCode(this)" style="width:120px;"/>
<img src="getCode.do" id="CreateCheckCode" align="middle" title="点击刷新验证码" onclick="getCode()" style="cursor: pointer;">
<span id="checkCodeSpan" style="float: right;color: red;"></span>
</div>
</#if>
<div class="row">
<div class="col-xs-8">
<div class="checkbox icheck">
<label>
<input type="checkbox" name="remember" checked="checked" >记住密码
</label>
</div>
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="button" onclick="login()" class="btn btn-primary btn-block btn-flat">登录</button>
</div>
<!-- /.col -->
</div>
</form>
<script>
function login(){
$('#loginForm').form('submit',{
onSubmit: function(param){
var username = $('#loginForm input[name=username]').val();
if($.trim(username)==''){
alert('账号不能为空!')
$('#loginForm input[name=username]').focus();
return false;
}
var p = $('#loginForm #password').val();
if($.trim(p)==''){
alert('密码不能为空!')
$('#loginForm #password').focus();
return false;
}
var checkCodeInput = $('#loginForm #checkCode');
if(checkCodeInput.length>0){//判断元素是否存在
var checkCode = checkCodeInput.val();
if($.trim(checkCode)=='' || checkCode.length!=4 ){
alert('请输入4位验证码!')
checkCodeInput.select();
checkCodeInput.focus();
return false;
}
}
var key = $('#KEY').val();
key = CryptoJS.enc.Utf8.parse(key);
p = CryptoJS.enc.Utf8.parse($.trim(p));
var encrypted = CryptoJS.AES.encrypt(p, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
param.password = encrypted.toString();
},
success:function(data){
var data = eval('(' + data + ')');
if (data.success){
window.location.href = '${request.contextPath}/';
}else{
if(data.info=='timeout'){//可能已经就登录了,无需再次登录
alert('登录超时或已经登录!');
window.location.href = '${request.contextPath}/';
}else if('checkCode'==data.info){//需要验证码了
alert('用户名或密码错误!');
window.location.href = 'login';
}else if('codeError'==data.info){//验证码错误
getCode();
$('#checkCodeSpan').text('验证码错误');
$('#loginForm #checkCode').select();
$('#loginForm #checkCode').focus();
}else{
//登录失败,更新login_token
$('#KEY').val(data.data);
if($('#checkCodeSpan')){
$('#checkCodeSpan').text('');
}
alert(data.info);
}
}
}
}) ;
}
function getCode(){
$("#CreateCheckCode").attr('src',"getCode.do?nocache=" + new Date().getTime());
}