2.6.MD5算法与Salt
MD5算法的作用和特点
- 作用:一般用来加密或者签名(校验和)
- 特点:MD5算法不可逆,如果内容相同无论执行多少次md5生成结果始终是一致
Salt
我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。
**加Salt可以一定程度上解决这一问题。所谓加Salt方法,就是加点“佐料”。**其基本想法是这样的:当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,已确定密码是否正确。
这里的“佐料”被称作“Salt值”,这个值是由系统随机生成的,并且只有系统知道。这样,即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,他们的散列值也是不同的。即便黑客可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了(密码和salt值都得和黑客使用的一样才行)。
注册,登录流程
用户注册时,
- 用户输入【账号】和【密码】(以及其他用户信息);
- 系统为用户生成【Salt值】;
- 系统将【Salt值】和【用户密码】连接到一起;
- 对连接后的值进行散列,得到【Hash值】;
- 将【Hash值1】和【Salt值】分别放到数据库中。
用户登录时,
- 用户输入【账号】和【密码】;
- 系统通过用户名找到与之对应的【Hash值】和【Salt值】;
- 系统将【Salt值】和【用户输入的密码】连接到一起;
- 对连接后的值进行散列,得到【Hash值2】(注意是即时运算出来的值);
- 比较【Hash值1】和【Hash值2】是否相等,相等则表示密码正确,否则表示密码错误。
注意:有时候,为了减轻开发压力,程序员会统一使用一个salt值(储存在某个地方),而不是每个用户都生成私有的salt值。
实现
对密码使用MD5+Slat,并进行hash加密
// 这里的用户名密码是为了模拟提交的身份信息
static final String user_name = "张三";
static final String password = "123456";
// 使用固定的slat值加密
static final String slat = "we#ewe^sd*";
// 使用hash加密的次数
static final int hashInterations = 1024;
public static void main(String[] args) {
Md5Hash md5Hash;
// 使用md5
md5Hash = new Md5Hash(password);
System.out.println(md5Hash.toHex());
// 使用md5+slat
md5Hash = new Md5Hash(password, slat);
System.out.println(md5Hash.toHex());
// 使用md5+slat+hash散列
md5Hash = new Md5Hash(password, slat, hashInterations);
System.out.println(md5Hash.toHex());
}
自定义realm实现在用户登录的时候进行比对
1,自定义realm
public class MD5AndSaltRealm extends AuthenticatingRealm {
// 使用固定的slat值加密
static final String slat = "we#ewe^sd*";
/**
*
* 1. 用户输入【账号】和【密码】;
*
* 2. 系统通过用户名找到与之对应的【Hash值】和【Salt值】;
*
* 3.系统将【Salt值】和【用户输入的密码】连接到一起;
*
* 4. 对连接后的值进行散列,得到【Hash值2】(注意是即时运算出来的值);
*
* 5.比较【Hash值1】和【Hash值2】是否相等,相等则表示密码正确,否则表示密码错误。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 模拟从数据库查询出来的数据
String username = "张三";
// 存储在数据库中不再是明文密码,而是经过md5+slat之后的密文
String hasdcrential = "4166a9a6e14420ef718f9b59d4adc0fb";
// 1.根据token取出用户提交的信息
UsernamePasswordToken user_token = (UsernamePasswordToken) token;
// 2.判断用户名是否与数据库内的匹配
if (!user_token.getUsername().equals(username)) {
throw new UnknownAccountException("无此用户");
}
// 3.将用户的信息提交
// 参数1 是用户的身份
// 参数2 从数据库取出的经过md5和slat加工之后的密码
// 参数3 你的slat
// 参数4 当前realm的name
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user_token.getPrincipal(), hasdcrential,
ByteSource.Util.bytes(slat), this.getName());
return info;
}
}
2,验证
public class TestAuthentication03 {
// 使用hash加密的次数
static final int hashInterations = 1024;
public static void main(String[] args) {
// 创建安全管理器
DefaultSecurityManager securityMananger = new DefaultSecurityManager();
// 创建realm
MD5AndSaltRealm realm = new MD5AndSaltRealm();
// 创建realm使用的hash凭证器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 告诉凭证器加密的类型
matcher.setHashAlgorithmName("md5");
// 告诉凭证器使用hash散列加密的次数
matcher.setHashIterations(1024);
// 注入realm
realm.setCredentialsMatcher(matcher);
// 给securityMananger注入realm
securityMananger.setRealm(realm);
// 将securityMananger给安全管理工具
SecurityUtils.setSecurityManager(securityMananger);
// 获取subject
Subject subject = SecurityUtils.getSubject();
// 模拟用户提交的数据
UsernamePasswordToken token = new UsernamePasswordToken("张三", "123456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}