• (二十八)动态盐的MD5加密算法(java实现)


    目录

    后来我发现,BCryptPasswordEncoder 是这个思路的实现的最优解,如果你看到这篇博客,可以不用看了,直接使用 spring securityBCryptPasswordEncoder


    源代码:

    package ijava.xin.utils;
    
    import sun.misc.BASE64Encoder;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    
    /**
     *  动态盐的MD5加密
     * @author An
     */
    @SuppressWarnings("unused")
    public class Md5Util {
    
    
        @SuppressWarnings("unused")
        /**
         * 普通MD5,只是实现下,不推荐使用,是不可逆的,但是聪明的人想到了查表,导致普通MD5的安全壁垒 GG 了;
         * @author AN
         * @time 2018年7月26日10:55:01
         * @return 加密的字符
         */
        public static String MD5(String input) {
            MessageDigest md5 = null;
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                return "JDK不支持该算法,检查下JDK";
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
            
            byte[] byteArray = input.getBytes();
    
            byte[] md5Bytes = md5.digest(byteArray);
    
            System.out.println( new BASE64Encoder().encode(md5Bytes));
            StringBuffer hexValue = new StringBuffer();
            //     将加密完的字符串,全部转成0-9、a-f的字符串
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;
    		//     如果小于16,也就是16进制只有1位的情况下。前面补0
                if (val < 16) {
                    hexValue.append("0");
                }
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();
    
        }
    
    
        /**
         * 加盐MD5
         *
         * @param password 原始密码
         * @return 加盐的MD5字符串
         * @author An
         * @time 2018年7月26日10:55:55
         */
        public static String generate(String password) {
    //        生成随机盐,长度12位
            byte[] bytes = new byte[12];
            SecureRandom random = new SecureRandom();
            random.nextBytes(bytes);
    
            StringBuilder builder = new StringBuilder();
    //        将字节数组变为字符串
            for (int i = 0; i < bytes.length; i++) {
    //            将生成的值,全部映射到0-255 之间
                int val = ((int) bytes[i]) & 0xff;
                if (val < 16) {
    //                为了控制盐的长度,这里小于16 的值,我们将它补为 大于16的值;
    //                这样,生的盐的长度是固定的:bytes * 2 ;
                    builder.append(Integer.toHexString(val + 16));
                } else {
                    builder.append(Integer.toHexString(val));
                }
            }
    
    //        最终的盐,长度是 12*2 = 24 ;
            String salt = builder.toString();
    
    
    //        先加盐Md5一把,再将 MD5 转换成 24位的 base64 位编码
            password = md5Hex(password + salt);
    
            char[] cs = new char[salt.length() + password.length()];
    
            for (int i = 0; i < cs.length; i += 4) {
    
    //            密码编码
                cs[i] = password.charAt(i / 2);
                cs[i + 2] = password.charAt(i / 2 + 1);
    //            盐编码
                cs[i + 1] = salt.charAt(i / 2);
                cs[i + 3] = salt.charAt(i / 2 + 1);
    
            }
            return new String(cs);
        }
    
        /**
         * 校验加盐后是否和原文一致
         *
         * @param password
         * @param md5
         * @return true 代表密码验证通过
         * @author AN
         * @time 2018年7月26日10:56:24
         */
        public static boolean verify(String password, String md5) {
    //        解码密码
            char[] cs1 = new char[24];
    //        解码盐
            char[] cs2 = new char[24];
    //        从MD5 中取出盐
            for (int i = 0; i < md5.length(); i += 4) {
    //            取出盐
                cs2[i / 2] = md5.charAt(i + 1);
                cs2[i / 2 + 1] = md5.charAt(i + 3);
    //            取出密码的MD5值(经过Base64转换后的MD5)
                cs1[i / 2] = md5.charAt(i + 0);
                cs1[i / 2 + 1] = md5.charAt(i + 2);
            }
    
            String salt = new String(cs2);
    
            return md5Hex(password + salt).equals(new String(cs1));
        }
    
        /**
         * 获取十六进制字符串形式的MD5摘要
         */
        private static String md5Hex(String src) {
            try {
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                byte[] bs = md5.digest(src.getBytes());
                return new String(new BASE64Encoder().encode(bs));
            } catch (Exception e) {
                return null;
            }
        }
    
    }
    
    
    

    函数用法讲解:

    • 获取动态加盐的MD5串

      使用 public static String generate(String password) 方法;将原始密码传进去,返回加密过后的密码;

    • 检验密码是否正确

      使用 public static boolean verify(String password, String md5)参数一密码参数二加密过后的密码,方法内部对参数一的密码进行加密,与参数二进行比对,一致就代表密码正确;


    用法代码实例:

    	    // 原文
            String plaintext = "621959";
            //  plaintext = "123456";
            System.out.println("原始:" + plaintext);
            System.out.println("普通MD5后:" + Md5Util.MD5(plaintext));
    
            // 获取加盐后的MD5值
            String ciphertext = Md5Util.generate(plaintext);
            System.out.println("加盐MD5字符串的长度"+ciphertext.length());
            System.out.println("加盐后MD5:" + ciphertext);
            System.out.println("是否是同一字符串:" + Md5Util.verify(plaintext, ciphertext));
    

    输出:

    原始:621959
    普通MD5后:be28f86b08d8a8fd19fbc02fcb114aec
    加盐MD5字符串的长度48
    加盐后MD5:F27812O70foeqcD6x1h0GcQ3U2c5e9ce99x3u1hfubw9=2=9
    是否是同一字符串:true
    

    对比普通 MD5 的优点

    • 普通MD5

    MD5 是一种 不可逆 的加密算法,也就是说,你无法从加密过后的字符串中,逆向推导出原始字符串;

    既然无法逆向推导出,那么为什么普通的 MD5 ,又是那么的脆弱不堪呢?

    因为 普通MD5 的加密,是不变的,即同一个密码的加密,永远是相同的;

    后来不知道从哪天起,坏人之间流行起了 “彩虹表”,这个神兵利器正是利用它的不变性, 彩虹表 中记录着一个密码链,它将一个加密的密码的各种可能的原始密码,记录在表中;

    比如 13daddsd ,是 “123” 的 MD5 加密,是 “145” 的 MD2 的加密,是 “121” 的 SHA 的加密字符串;

    这样当坏人们,获取到 13daddsd 这样的一条加密字符串以后,它们就去查彩虹表,得到原始密码可能是:"123"、“121”、“145” ;很快就能破译出原始密码!

    • 固定盐MD5

    加盐的MD5,由于坏人们,不知道加的盐是什么,盐是怎么加进去,他要想再生成彩虹表,基本是不可行的,因为,需要为一个网站的一种盐、一种加盐方式生成一个彩虹表,并且这张彩虹表仅仅对这个网站有效,对其他站点,是无效的;若想攻破多个站点,需要多张彩虹表,,这样的工程是吓人的!并且他们也很难知道,我们的盐是什么,怎么加的;

    既然加盐已经很安全了,为什么还需要动态的盐呢;因为,我们还需要防止数据库管理员,防止内鬼拖库

    对于固定的盐,相同的密码生成的字符串,也是相同的;黑客由于不知道盐是什么,不想费劲的去做;而数据库管理员,管理数据库,加密的密码,就摆在眼前,他只要自己生成一些密码,就可以知道哪些用户的密码是什么了。

    比如,他对“2312”进行加密,得到了“dwe121d12”,然后检索数据库,只要是加密的密码字符串是 dwe121d12 ,那么原始密码都是“2312”;

    • 动态盐MD5

    因此,我们需要动态盐;动态盐,因为盐是动态的,每次都不同,然后和密码混在一起,加密;即使是相同的密码,每次生成的加密字符串也不一样;


    实现思路:

    • 简单MD5

      底层加密算法来自 MessageDigest 类,先将原始密码转换成**字节数组,然后进行加密计算,得到一个加密过后的字节数组;对加密过后的数组进行处理,防止传输的过程中丢失信息,应为加密后的字符串,在传输的过程中,可能会出现UNICODE编码识别的字符;因此,进行处理一下,可以选择直接使用Base64编码,将它们全部转成码值在0 - 63**之间的字符;

      这里我没有用 Base64 编码,自己写了一个算法,将加密的字符串转成**0-9、a-f**的字符串;思想都一样;

    • 动态盐MD5

    1. 动态盐的生成

      使用随机数生成,使用 SecureRandom 类,生成随机数;生成的随机数填充在字节数组中,然后将字节数组转成字符串,转之前,同样进行一次转换,将它们全部转成**0-9、a-f**的字符串;

      转到时候,需要注意下,为了控制盐的最终长度,对小于16的数值,进行了加16,为了得到2位数的16进制,得到动态盐;

    2. 将盐加在密码屁股后面,进行一个MD5加密,得到加密的字符串

    3. 动态盐 嵌套到返回的加密字符串中,为了下次验证试验;

      嵌套的时候,注意奇数位是密码,偶数位是盐,由于之前控制了盐的长度是**24**,MD5经过 Base64 编码的长度也是 24 ;(*其中 Base64 编码的长度:设字符串长度为n ,长度为 ⌈n/3⌉4 ⌈⌉ 代表上取整

      由于控制了长度是固定的48位,因此,嵌套的时候,很容易嵌套;

    4. 验证

      从给定的加密的字符串中获取当时的 动态盐、加盐的 MD5 字符串,根据当初嵌套的规则取;

      然后将传入的密码,用加密的盐混合一下,再MD5,进行比对,即可得知密码是否一致;

  • 相关阅读:
    Linux运维工作总结教训
    java-GC
    java设计模式-原形模式
    java-桥接模式
    java-装饰者模式
    java-正则表达式
    java设计模式-建造者模式
    Python 条件与循环
    Python 集合、字典、运算符
    Python 字符串拼接、格式化输出、深浅复制
  • 原文地址:https://www.cnblogs.com/young-youth/p/11665693.html
Copyright © 2020-2023  润新知