一、第一代密码
第一代密码用户名的密码直接保存在服务器的数据库上,一旦数据库泄漏,数据库里的用户名和密码都是明文的。
二、第二代密码
第二代密码将用户名密码用MD5算法加密,把加密后的密码存入服务器数据库中。当用户下次输入密码登陆的时候可以经过MD5运算,将运算值和数据库中的密码比较,如果一致代表密码正确,匹配上。如果数据库泄漏,得到的也只是经过MD5加密的密码。
但是这里就有问题了,会有好事者收集常用密码,经过MD5运算,把密码和加密后的密码做成一个数据字典。如果我们的密码比较简单,正好在那个数据字典里,一旦后台数据库泄漏,用户的密码也能知道。现在网上有网站能查询MD5有没有被破解。
三、第三代密码
加盐处理。随机生成一系列的值,叫作salt,放在用户名的开头,中间或者最后,这个自己服务器约定即可,然后将新生成的密码用MD5运算,得到新的密码,存入数据库中,记得salt也要存入数据库中。这样在用户下次登录的时候也可以验证。
加盐的好处使得密码更难破解。
比如说我有一个数据字典库有N = 500W条密码。泄漏的数据库里有M = 30万条代码。如果没加盐,我得到泄漏的数据库后,一一和数据字典比对就好,碰到相同的,我就知道了原来的密码了。全部比对完的时间复杂度是O(M*N)。
如果加盐后,有30万条盐。我要破解一条的代价是 我要从数据字典里选一条明文密码,在泄漏的数据库中选一条盐,匹配进行MD5运算,然后在数据字典里查询。时间复杂度是O(N),一共要进行M*N次比对,所以时间复杂度是O(M*N*N)。破解难度大大提高。
MD5算法的代码如下

public static String MD5(String key) {
        char hexDigits[] = {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };
        try {
            byte[] btInput = key.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            logger.error("生成MD5失败", e);
            return null;
        }
    }
user = new User();
        user.setName(username);
        user.setSalt(UUID.randomUUID().toString().substring(0, 5)); //加盐
        String head = String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000));
        user.setHeadUrl(head);
        user.setPassword(ToutiaoUtil.MD5(password+user.getSalt())); //MD5运算
        userDAO.addUser(user);