流程:
用户注册:
输入用户名,密码,确认密码后,点击提交时
首先生成一份随机盐salt
function encrypt(source, salt) { for (i = 0; i < 100000; i++) { source = $.md5(source + salt); } return source; }
将密码和盐传入,因为要慢加密,所以使密码加密了10W次,大约是0.6秒左右,将加密后的字符串作为密码与用户名和盐一起传到后台
后台再将密码和盐做一次md5加密,方法可以和前台不同作为密文保存在数据库中
用户登录时,输入用户名,密码
在输入完用户名时,ajax请求后台,判断用户名是否存在的同时,将存在的用户名所对应的盐返回给前台
同步骤2,慢加密密码,
这时,会重新生成一次盐,同步骤2,再慢加密一次密码,得到第二份慢加密密码,然后将用户名、第一次慢加密密码、第二份盐、第二份慢加密密码传到后台
后台再将密码和数据库中的盐做一次md5加密,和数据库中的密码比对,一样表示密码正确,同时存入6中的第二份盐和第二份密码,将数据库中的密码和盐更新了。
可以看出再次过程中相当于自动为用户更新了密码。这种方法可以有效的对抗彩虹表这种暴力破解的方式,有效的提高了密码破解难度。
下面是一个实现的demo。
因为最近在学习jfinal,所以这个demo是使用的Jfianl
从底层起,数据库用的mysql,建立一个数据库demo,建立一张表user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(32) NOT NULL,
`tryCount` int(4) NOT NULL,
`salt` int(4) NOT NULL,
`regTime` datetime NOT NULL,
`updateTime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
建立一个Controller
package com.chmin.demo.controller;
import com.chmin.demo.model.User;
import com.chmin.demo.service.UserService;
import com.jfinal.core.Controller;
/**
* @author chmin
* @time 2016年2月14日 下午2:21:26
*/
public class UserController extends Controller {
private UserService userService = new UserService();
public void index() {
toLogin();
}
/**
* 跳转到注册页面
*/
public void toReg() {
renderJsp("reg.jsp");
}
/**
* 跳转到登陆页面
*/
public void toLogin() {
renderJsp("login.jsp");
}
/**
* 获取用户盐,并验证用户名是否存在
*/
public void getSalt(){
User user = getModel(User.class);
renderJson(userService.getSalt(user));
}
/**
* 用户注册
*/
public void reg() {
User user = getModel(User.class);
renderText(userService.save(user));
}
/**
* 用户登陆
*/
public void login() {
User user = getModel(User.class);
String newPassword = getPara("newPassword");
renderText(userService.login(user, newPassword));
}
}
和一个service
package com.chmin.demo.service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.chmin.demo.model.User;
import com.chmin.demo.util.Md5Util;
import com.chmin.demo.util.StringUtil;
/**
* @author chmin
* @time 2016年2月14日 下午4:24:08
*/
public class UserService {
/**
* 保存用户
*
* @param user
* @return
*/
public String save(User user) {
if (user == null) {
return "系统繁忙,请稍后重试!";
} else if (StringUtil.isEmpty(user.getUsername())) {
return "请输入用户名!";
} else if (StringUtil.isEmpty(user.getPassword())) {
return "请输入密码!";
} else if (StringUtil.isEmpty(user.getSalt()+"")) {
return "系统繁忙,请稍后重试!";
} else if (User.dao.nameUsed(user.getUsername())) {
return "用户名已被使用!";
} else {
user.setPassword(Md5Util.encrypt(user.getPassword() + user.getSalt()));
user.setRegTime(new Date());
user.setTryCount(0);
return user.save() ? "用户注册成功!" : "系统繁忙,请稍后重试!";
}
}
/**
* 登陆操作
* @param user
* @param newPassword
* @return
*/
public String login(User user, String newPassword){
User user2 = null;
if (user == null || StringUtil.isEmpty(user.getUsername())) {
return "系统繁忙,请稍后重试!";
} else if ((user2 = User.dao.findFirst("select * from user where username = ?", user.getUsername())) == null){
return "用户不存在!";
} else if (user2.getTryCount() >= 5){
return "用户已被冻结!";
} else {
user.setPassword(Md5Util.encrypt(user.getPassword() + user2.getSalt()));
if(user.getPassword().equals(user2.getPassword())){
user2.setSalt(user.getSalt());
user2.setPassword(Md5Util.encrypt(newPassword + user.getSalt()));
user2.setTryCount(0);
user2.update();
return "true";
} else {
user2.setTryCount(user2.getTryCount() + 1);
user2.update();
return "密码错误!还有" + (5 - user2.getTryCount()) + "次尝试机会!";
}
}
}
/**
* 获取用户盐操作
* @param user
* @return
*/
public List<Object> getSalt(User user){
List<Object> result = new ArrayList<>();
boolean flag = false;
String msg = null;
if (user == null || StringUtil.isEmpty(user.getUsername())) {
msg = "系统繁忙,请稍后重试!";
} else if ((user = User.dao.findFirst("select * from user where username = ?", user.getUsername())) == null){
msg = "用户不存在!";
} else {
msg = user.getSalt() + "";
flag = true;
}
System.out.println(msg);
result.add(flag);
result.add(msg);
return result;
}
}
前台的注册页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE>
<html>
<head>
<base href="<%=basePath%>">
<title>登陆页面</title>
<meta name="pragma" content="no-cache">
<meta name="cache-control" content="no-cache">
<meta name="expires" content="0">
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="RegPage">
<script src="js/jquery-2.1.3.min.js"></script>
<script src="js/jquery.md5.js"></script>
</head>
<body>
<div style="margin: 300px auto; width: 360px;">
<form id="reg">
<div style="width: 360px;">
<span style="text-align: right; width: 120px; display: inline-block; margin-bottom: 10px;">用户名:</span>
<input type="text" id="username" name="user.username">
</div>
<div style="width: 360px;">
<span style="text-align: right; width: 120px; display: inline-block; margin-bottom: 10px;">密码:</span>
<input type="password" id="password" name="user.password">
</div>
<div style="width: 360px;">
<span style="text-align: right; width: 120px; display: inline-block; margin-bottom: 10px;">验证密码:</span>
<input type="password" id="password2">
</div>
<div style="width: 360px;">
<span style="text-align: right; width: 140px; display: inline-block;"></span>
<a href="user/toLogin"><input type="button" value="Login"></a>
<a href="javascript:reg()"><input type="button" value="Register"></a>
</div>
<input type="hidden" id="salt" name="user.salt">
</form>
</div>
</body>
<script type="text/javascript">
function reg() {
var username = $("#username").val().trim();
if (username.length == 0) {
alert("用户名不能为空!");
return;
}
var password = $("#password").val().trim();
if (password.length == 0) {
alert("密码不能为空!");
return;
}
var password2 = $("#password2").val().trim();
if (password2.length == 0) {
alert("密码不能为空!");
return;
}
if (password != password2) {
alert("两次密码不一致!");
return;
}
var salt = getSalt();
$("#salt").val(salt);
$("#password").val(encrypt(password, salt));
$.ajax({
type: "post",
data : $("#reg").serialize(),
url : "user/reg",
success : function(msg) {
alert(msg);
if(msg == "用户注册成功!"){
window.location.href = "user/toLogin";
}
}
})
}
function getSalt() {
return ~~(Math.random() * 9000 + 1000);
}
function encrypt(source, salt) {
for (i = 0; i < 100000; i++) {
source = $.md5(source + salt);
}
return source;
}
</script>
</html>
登陆页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE>
<html>
<head>
<base href="<%=basePath%>">
<title>登陆页面</title>
<meta name="pragma" content="no-cache">
<meta name="cache-control" content="no-cache">
<meta name="expires" content="0">
<meta name="keywords" content="keyword1,keyword2,keyword3">
<meta name="description" content="RegPage">
<script src="js/jquery-2.1.3.min.js"></script>
<script src="js/jquery.md5.js"></script>
</head>
<body>
<div style="margin: 300px auto; width: 450px;">
<form id="login">
<div style="width: 450px;">
<span style="text-align: right; width: 120px; display: inline-block; margin-bottom: 10px;">用户名:</span>
<input type="text" id="username" name="user.username" onblur="javascript:getSalt()"><span style="color: red;" id="usernameError"></span>
</div>
<div style="width: 450px;">
<span style="text-align: right; width: 120px; display: inline-block; margin-bottom: 10px;">密码:</span>
<input type="password" id="password" name="user.password">
</div>
<div style="width: 450px;">
<span style="text-align: right; width: 140px; display: inline-block;"></span>
<a href="javascript:login()"><input type="button" value="Login"></a>
<a href="user/toReg"><input type="button" value="Register"></a>
</div>
<input type="hidden" id="salt" name="user.salt">
<input type="hidden" id="newPassword" name="newPassword">
</form>
</div>
</body>
<script type="text/javascript">
function login(){
var username = $("#username").val().trim();
if(username.length == 0){
alert("用户名不能为空!");
return;
}
var password = $("#password").val().trim();
if(password.length == 0){
alert("密码不能为空!");
return;
}
var salt = $("#salt").val();
$("#password").val(encrypt(password, salt));
salt = ~~(Math.random() * 9000 + 1000);
$("#salt").val(salt);
$("#newPassword").val(encrypt(password, salt));
$.ajax({
type: "post",
data : $("#login").serialize(),
url : "user/login",
success : function(msg) {
if(msg){
window.location.href = "user/toReg";
} else {
alert(msg);
}
}
})
}
function getSalt() {
$.ajax({
type: "post",
data : $("#login").serialize(),
url : "user/getSalt",
success : function(msg) {
if (msg[0]) {
$("#salt").val(msg[1]);
$("#usernameError").html("");
} else {
$("#salt").val("");
$("#usernameError").html(msg[1]);
}
}
})
}
function encrypt(source, salt) {
for (i = 0; i < 100000; i++) {
source = $.md5(source + salt);
}
return source;
}
</script>
</html>