锁定老帖子 主题:一种简单的给MD5加盐算法
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (3)
|
|
---|---|
作者 | 正文 |
发表时间:2012-10-19
最后修改:2012-10-25
加盐的算法有很多,考虑到加盐的目的(防止拥有系统底层权限的人员),想做到绝对不可反查是很困难的,需要有其他软件或者硬件的协助,在很多场景下的实用性比较差。如果只是想增加反查的难度,倒是有很多方法可以选择,一种便利的方法是md5(Password+UserName),即将用户名和密码字符串相加再MD5,这样的MD5摘要基本上不可反查。但有时候用户名可能会发生变化,发生变化后密码即不可用了(验证密码实际上就是再次计算摘要的过程)。 因此我们做了一个非常简单的算法,每次保存密码到数据库时,都生成一个随机16位数字,将这16位数字和密码相加再求MD5摘要,然后在摘要中再将这16位数字按规则掺入形成一个48位的字符串。在验证密码时再从48位字符串中按规则提取16位数字,和用户输入的密码相加再MD5。按照这种方法形成的结果肯定是不可直接反查的,且同一个密码每次保存时形成的摘要也都是不同的。如以下代码所示: package com.zving.framework.security; import java.security.MessageDigest; import java.util.Random; import org.apache.commons.codec.binary.Hex; /** * @author wyuch * @email wyuch@zving.com * @date 2011-12-31 */ public class PasswordUtil { /** * 生成含有随机盐的密码 */ public static String generate(String password) { Random r = new Random(); StringBuilder sb = new StringBuilder(16); sb.append(r.nextInt(99999999)).append(r.nextInt(99999999)); int len = sb.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { sb.append("0"); } } String salt = sb.toString(); password = md5Hex(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return new String(cs); } /** * 校验密码是否正确 */ public static boolean verify(String password, String md5) { char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 48; i += 3) { cs1[i / 3 * 2] = md5.charAt(i); cs1[i / 3 * 2 + 1] = md5.charAt(i + 2); cs2[i / 3] = md5.charAt(i + 1); } String salt = new String(cs2); return md5Hex(password + salt).equals(new String(cs1)); } /** * 获取十六进制字符串形式的MD5摘要 */ public static String md5Hex(String src) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bs = md5.digest(src.getBytes()); return new String(new Hex().encode(bs)); } catch (Exception e) { return null; } } public static void main(String[] args) { String password = generate("admin"); System.out.println(verify("admin", password)); } } --------------------------回应楼下几位的问题--------------------------- 1、这是一个很简单的算法,和绝大部分加盐算法一样,只是为了增加通过MD5数据库反查(例如使用http://www.md5.cc/类似的反查数据库)的难度,并不能保证绝对不可反查。 2、作者假定,能够拿到一个应用中用户密码的MD5摘要的人,也能获得该应用的密码摘要形成的算法,以及该算法依赖的外部因素(持久层中的用户ID、用户名、加盐规则、随机数据等)。因此不管是什么样的算法(例如多次MD5以及各种加盐规则),破解者都可以通过将手头的MD5反查库根据你的算法和数据重新运算一遍,以获得针对你的用户数据库的专用的MD5反查库,然后再逐一反查密码明文。 3、考虑到用户密码验证实际上是再次形成MD5摘要的过程,而用户密码验证是一个经常发生的行为,会有高并发的情况。所以一般用户密码验证不允许采用非常复杂和消耗性能的算法,一次验证消耗的CPU时间应在100毫秒之内。我们采用MD5或SHA1的原因一是久经考验的摘要不可逆性;二是性能在允许的范围之内(对于密码这样的短字符串,一台普通PC每秒都可以运算上万次)。 4、因为第3点提到的性能要求,所以根据你的算法重新构建一个专用MD5反查库的时间不会特别长(采用较好的机器和并行计算,可以在有意义的时间范围之内重新形成有100亿条以上记录的MD5反查库)。 5、根据以上2/3/4点,不管采用什么加盐算法,只要算法可以获得,则破解者都可以破解任意一个用户的密码的明文(准确地说是验证该密码是否在MD5反查库中存在,如果存在则能获得明文。特别复杂的密码在MD5反查库中不会有记录,因此即使不加盐也无法破解)。 6、但多次MD5、打乱MD5结果的数位顺序这些算法的安全性要更差一些,便如多次MD5,不管你是几次MD5,破解者只需要根据你算法运算形成MD5反查库,则一次性就破解了你的所有密码。 7、本文中的算法以及MD5(UserName+Password+Salt)、MD5(UserID+Password+Salt)等,是在密码之外有引入了干扰项(称之为盐),因此破解者必须为每一个密码单独形成一个MD5反查库,代价就会非常高,要想破解所有密码实际上已经不可行了。 8、但MD5(UserName+Password+Salt)要求UserName永远不变,MD5(UserID+Password+Salt)要求用户在用户名之外必须有一个不变的UserID。这些额外的要求,导致其适用性没有本文中的算法好。 9、如果以上情况有一条你不需要考虑,则本算法对你确实没有什么用处,就当作者自卖自夸灌水吧。因为本算法有一点点聊胜于无的优势,所以发个帖子请大家斧正,对回帖的兄弟们表示感谢。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-10-19
改了一下,不需要引用其他类了。
|
|
返回顶楼 | |
发表时间:2012-10-19
最后修改:2012-10-19
要防止MD5解密办法多的是。
比如加盐、多次加密、打乱顺序、添加干扰位等。 一般情况下对原字符串MD5加密后打乱顺序就行了。即保密又不会增加MD5字符串的长度。 |
|
返回顶楼 | |
发表时间:2012-10-19
mfkvfn 写道 要防止MD5解密办法多的是。
比如加盐、多次加密、打乱顺序、添加干扰位等。 一般情况下对原字符串MD5加密后打乱顺序就行了。即保密又不会增加MD5字符串的长度。 我想加入点随机性,让同一个密码形成不同的MD5结果,以避免破解了一个就破解了所有相同的密码。 |
|
返回顶楼 | |
发表时间:2012-10-19
别人如果知道打乱的顺序了当然可以破解。
同样的,如果你的方法,别人知道了哪几位是盐,还不是一样可以破解。 |
|
返回顶楼 | |
发表时间:2012-10-19
mfkvfn 写道 别人如果知道打乱的顺序了当然可以破解。
同样的,如果你的方法,别人知道了哪几位是盐,还不是一样可以破解。 额,肯定是都能破解的,如果密码比较常见的话,别人根据算法和MD5数据库中的记录重新跑一遍,就能把你的MD5结果和他的数据库中的结果中己有记录匹配上,只是个运算时间的问题。 我的意思是说如果有N个用户都用某个常用密码(便如123456),则打乱顺序这N个用户只需要计算一次就行了,如果有随机性,则需要运算N次。 算是聊胜于无吧。 |
|
返回顶楼 | |
发表时间:2012-10-19
为什么不选择多次加密呢?
|
|
返回顶楼 | |
发表时间:2012-10-19
最后修改:2012-10-19
原来有个朋友介绍的一种方法,不算很保险,但也没太大问题。
同样也是UserName+PassWord再MD5后,截取固定长度。 就是不保存原始的MD5,不影响验证,也最简单。 |
|
返回顶楼 | |
发表时间:2012-10-20
我觉的有一种方法还是比较好的;
假设:user(账户)、passwd(密码)、security(盐码6-8) security(盐码):默认是123456; userpassword:passwd和security加密后的字符串; 数据库保存的是user、userpassword; user、security和passwd由用户存放; 用户验证时: 输入user、passwd、security; 系统验证账户密码时:根据passwd和security生成的加密字符串,和数据库保存的密码比对即可; 客户的原始密码就不能根据数据库中存的破解了; |
|
返回顶楼 | |
发表时间:2012-10-20
作为一个加密函数,你这程序写的性能未免有点低。
首先你Salt补零的过程用循环就会是一个问题。你为什么不初始化一个用16个‘0’的字符串初始化一个StringBuffer,然后再作后面的工作就可以了。 对于后面48位密码产生也是同样的道理。 还有基本上你这种加密没什么意义,因为48位密码的混合顺序是固定的(人为的),而不是consistent的随机产生。 |
|
返回顶楼 | |