(二十八)动态盐的MD5加密算法(java实现)
目录
后来我发现,
BCryptPasswordEncoder是这个思路的实现的最优解,如果你看到这篇博客,可以不用看了,直接使用spring security的BCryptPasswordEncoder;
源代码:
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 
- 动态盐的生成 - 使用随机数生成,使用 - SecureRandom类,生成随机数;生成的随机数填充在字节数组中,然后将字节数组转成字符串,转之前,同样进行一次转换,将它们全部转成**- 0-9、a-f**的字符串;- 转到时候,需要注意下, - 为了控制盐的最终长度,对小于16的数值,进行了加16,为了得到2位数的16进制,得到动态盐;
- 将盐加在密码屁股后面,进行一个MD5加密,得到加密的字符串 
- 将 - 动态盐嵌套到返回的加密字符串中,为了下次验证试验;- 嵌套的时候,注意奇数位是密码,偶数位是盐,由于之前控制了盐的长度是** - 24**,MD5经过- Base64编码的长度也是- 24;(*其中- Base64编码的长度:设字符串长度为n ,长度为 ⌈n/3⌉4 ⌈⌉ 代表上取整)- 由于控制了长度是固定的48位,因此,嵌套的时候,很容易嵌套; 
- 验证 - 从给定的加密的字符串中获取当时的 - 动态盐、加盐的- MD5字符串,根据当初嵌套的规则取;- 然后将传入的密码,用加密的盐混合一下,再MD5,进行比对,即可得知密码是否一致; 
(二十八)动态盐的MD5加密算法(java实现)的更多相关文章
- Web 前端开发人员和设计师必读文章推荐【系列二十八】
		<Web 前端开发精华文章推荐>2014年第7期(总第28期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ... 
- VMware vSphere 服务器虚拟化之二十八 桌面虚拟化之安装View传输服务器
		VMware vSphere 服务器虚拟化之二十八 桌面虚拟化之安装View传输服务器 View 传输服务器用于管理和简化数据中心与在最终用户本地系统上检出使用的 View 桌面之间的数据传输.必须安 ... 
- Saiku使用iframe嵌入页面访问地址配置化(二十八)--DWR的基本使用
		Saiku使用iframe嵌入页面使用时ip与端口配置化(二十八)--DWR的基本使用 DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开 ... 
- Bootstrap <基础二十八>列表组
		列表组.列表组件用于以列表形式呈现复杂的和自定义的内容.创建一个基本的列表组的步骤如下: 向元素 <ul> 添加 class .list-group. 向 <li> 添加 cl ... 
- Citrix 服务器虚拟化之二十八 XenApp6.5发布文档内容
		Citrix 服务器虚拟化之二十八 XenApp 6.5发布文档内容 XenApp可发布以下类型的资源向用户提供信息访问,这些资源可在服务器或桌面上虚拟化: 1) 服务器桌面:发布场中服务器的整个 ... 
- WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]
		原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ... 
- Bootstrap入门(二十八)JS插件5:工具提醒
		Bootstrap入门(二十八)JS插件5:工具提醒 工具提示在使用过程中比较常见,但是实现起来有些麻烦,而bootstrap则很好地解决了这个问题. 我们来写一个简单的实例 先引入CSS文件和JS文 ... 
- mysql进阶(二十八)MySQL GRANT REVOKE用法
		mysql进阶(二十八)MySQL GRANT REVOKE用法 MySQL的权限系统围绕着两个概念: 认证->确定用户是否允许连接数据库服务器: 授权->确定用户是否拥有足够的权限执 ... 
- JAVA之旅(二十八)——File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤
		JAVA之旅(二十八)--File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤 我们可以继续了,今天说下File 一.File概述 文件的操作是非常 ... 
随机推荐
- E.Substring Reverse Gym - 101755E
			Substring Reverse Problem Two strings s and t of the same length are given. Determine whether it is ... 
- 关于rsa公钥格式的处理,一行纯内容进行换行格式化
			最近在开发百度小程序,他的平台公钥是纯字符串,公钥的内容,没有rsa文件的头(-----BEGIN PUBLIC KEY-----)和尾部分-----END PUBLIC KEY----- 但是 PH ... 
- 2019巅峰极客CTF-web1(LOL英雄联盟)
			今晚有空 以后随缘写博客了 好好沉淀 web1当天做出的队伍很少 其实不难 折腾到最后就差一步 可惜 0x01 读取文件 截图没留了 只留了代码部分. 有个页面 有上传和下载功能 起初 ... 
- 关于在vue项目中使用wangEditor
			1,vue中安装wangEditor 使用的npm安装 npm install wangeditor --save 2,创建公用组件 在components中创建wangEnduit文件夹 组件内容为 ... 
- impdp导入报错ORA-39070:无法打开日志文件
			>impdp test/123456@orcl directory=expnc_dir dumpfile=TEST.DMP full=y ORA-39002:操作无效 ORA39070:无法打开 ... 
- HearthBuddy炉石兄弟 Method 'Entity.GetRace' not found.
			解决方案 namespace Triton.Game.Mapping{// Token: 0x020004A4 RID: 1188[Attribute38("Entity")]pu ... 
- [go]grpc远程接口调用实现
			// grpc序列化/反序列化成对应语言的对象 // 1.写idl(数据类型+方法) // 2.生成对应语言的序列化/反序列化代码 // 3.方法需要自己实现 // 环境(将gopath/bin加入p ... 
- SQL-W3School-高级:SQL NULL 函数
			ylbtech-SQL-W3School-高级:SQL NULL 函数 1.返回顶部 1. SQL ISNULL().NVL().IFNULL() 和 COALESCE() 函数 请看下面的 &quo ... 
- android指纹识别认证实现
			Android从6.0系统支持指纹认证功能 启动页面简单实现 package com.loaderman.samplecollect.zhiwen; import android.annotation ... 
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_04-freemarker基础-基础语法种类
			注释 编译一些这个模板 我的IDEA里面,是重新编译 刷新页面 注释.插值.FTL.文本 
