乐趣区

关于java:一文搞懂单向散列加密MD5SHA1SHA2SHA3

微信搜寻:码农 StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos

1. 定义

单向散列函数 (one-way hash function)是指对不同的输出值,通过单向散列函数进行计算,失去固定长度的输入值。这个输出值称为 音讯 (message),输入值称为 散列值(hash value)。

单向散列函数也被称为 音讯摘要函数 (message digest function)、 哈希函数 或者 杂凑函数 。输出的音讯也称为 原像 (pre-image)。输入的散列值也称为 音讯摘要 (message digest)或者 指纹(fingerprint),相当于该音讯的身份证。

单向散列函数有多种实现算法,常见的有:MD5SHA-1SHA-2SHA-3

2. 个性

通过下面的定义,咱们对单向散列函数的理解还是含糊的。上面介绍单向散列函数的个性,加深一下印象。

2.1 散列值长度固定

无论音讯的长度有多少,应用同一算法计算出的散列值长度总是固定的。比方 MD5 算法,无论输出多少,产生的散列值长度总是 128 比特(16 字节)。

然而比特是计算机可能辨认的单位,而咱们人类更习惯于应用十六进制字符串来示意(一个字节占用两位十六进制字符)。

2.2 音讯不同其散列值也不同

应用雷同的音讯,产生的散列值肯定雷同。

应用不同的音讯,产生的散列值也不雷同。哪怕只有一个比特的差异,失去的散列值也会有很大区别。

这一个性也叫做 抗碰撞性,对于抗碰撞性弱的算法,咱们不应该应用。

2.3 具备单向性

只能通过音讯计算出散列值,无奈通过散列值反算出音讯。

2.4 计算速度快

计算散列值的速度快。只管音讯越长,计算散列值的工夫也越长,但也会在短时间内实现。

3. 常见算法

MD5 与 SHA-1 算法已被攻破,不应该被用于新的用处;SHA-2 与 SHA-3 还是平安的,能够应用。

SHA- 2 包含:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

SHA- 3 包含:SHA3-224、SHA3-256、SHA3-384、SHA3-512。

算法名称 散列值长度 是否平安
MD5 128 不平安
SHA-1 160 不平安
SHA-224 224 平安
SHA-256 256 平安
SHA-384 384 平安
SHA-512 512 平安
SHA-512/224 224 平安
SHA-512/256 256 平安
SHA3-224 224 平安
SHA3-256 256 平安
SHA3-384 384 平安
SHA3-512 512 平安

4. 利用场景

单向散列函数并不能确保信息的机密性,它是一种保障信息完整性的明码技术。上面来看它的利用场景。

4.1 用户密码保护

用户在设置明码时,不记录明码自身,只记录明码的散列值,只有用户本人晓得明码的明文。校验明码时,只有输出的明码正确,失去的散列值肯定是一样的,示意校验正确。

为了避免彩虹表破解,还能够为明码进行加盐解决,只有验证明码时,应用雷同的盐即可实现校验。

应用散列值存储明码的益处是:即便数据库被盗,也无奈将密文反推出明文是什么,使明码保留更平安。

4.2 接口验签

为了保障接口的平安,能够采纳签名的形式发送。

发送者与接收者要有一个 共享秘钥 。当发送者向接收者发送申请时,参数中附加上签名(签名由 共享秘钥 + 业务参数,进行单向散列函数加密生成)。接收者收到后,应用雷同的形式生成签名,再与收到的签名进行比对,如果统一,验签胜利。

这样即能够验证业务参数是否被篡改,又能验明发送者的身份。

4.3 文件完整性校验

文件被挂载到网站时,同时也附上其散列值和算法,比方 Tomcat 官网。

用户下载后,计算其散列值,比照后果是否雷同,从而校验文件的完整性。

4.4 云盘秒传

当咱们将本人喜爱的视频放到网盘上时,发现只用了几秒的工夫就上传胜利了,而这个文件有几个 G 大小,是怎么做到的呢?

其实这个“秒传”性能能够利用单向散列函数来实现。

当咱们上传一个文件时,云盘客户端会先为该文件生成一个散列值。拿着这个散列值去数据库中匹配,如果匹配到,阐明该文件曾经在云服务器存在。只需将该散列值与用户进行关联,便可实现本次“上传”。

这样,一个文件在云服务器上只会存一份,大大节约了云服务器的空间。

5. 代码实现

JDK 的 java.security.MessageDigest 类为咱们提供了音讯摘要算法,用于 MD5 和 SHA 的散列值生成。上面代码做了简略的封装,便于间接应用。

public class MDUtil {

    /**
     * MD5 加密
     *
     * @param data 要加密的数据
     * @return 32 位十六进制字符串
     */
    public static String MD5(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * MD5 加密
     *
     * @param data 要加密的数据
     * @return 32 位十六进制字符串
     */
    public static String MD5(String data) {return MD5(data.getBytes());
    }

    /**
     * SHA-1 加密
     *
     * @param data 要加密的数据
     * @return 40 位十六进制字符串
     */
    public static String SHA1(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA-1 加密
     *
     * @param data 要加密的数据
     * @return 40 位十六进制字符串
     */
    public static String SHA1(String data) {return SHA1(data.getBytes());
    }

    /**
     * SHA-224 加密
     *
     * @param data 要加密的数据
     * @return 56 位十六进制字符串
     */
    public static String SHA224(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("SHA-224");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA-224 加密
     *
     * @param data 要加密的数据
     * @return 56 位十六进制字符串
     */
    public static String SHA224(String data) {return SHA224(data.getBytes());
    }

    /**
     * SHA-256 加密
     *
     * @param data 要加密的数据
     * @return 64 位十六进制字符串
     */
    public static String SHA256(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA-256 加密
     *
     * @param data 要加密的数据
     * @return 64 位十六进制字符串
     */
    public static String SHA256(String data) {return SHA256(data.getBytes());
    }

    /**
     * SHA-384 加密
     *
     * @param data 要加密的数据
     * @return 96 位十六进制字符串
     */
    public static String SHA384(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("SHA-384");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA-384 加密
     *
     * @param data 要加密的数据
     * @return 96 位十六进制字符串
     */
    public static String SHA384(String data) {return SHA384(data.getBytes());
    }

    /**
     * SHA-512 加密
     *
     * @param data 要加密的数据
     * @return 128 位十六进制字符串
     */
    public static String SHA512(byte[] data) {
        try {MessageDigest md = MessageDigest.getInstance("SHA-512");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
        return "";
    }

    /**
     * SHA-512 加密
     *
     * @param data 要加密的数据
     * @return 128 位十六进制字符串
     */
    public static String SHA512(String data) {return SHA512(data.getBytes());
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHexString(byte[] bytes) {StringBuilder hexValue = new StringBuilder();
        for (byte b : bytes) {
            int val = b & 0xFF;
            if (val < 16) {hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();}

}

上面别离应用这些算法计算“123456”的散列值:

public static void main(String[] args) {System.out.println("MD5\t\t" + MDUtil.MD5("123456"));
    System.out.println("SHA-1\t" + MDUtil.SHA1("123456"));
    System.out.println("SHA-224\t" + MDUtil.SHA224("123456"));
    System.out.println("SHA-256\t" + MDUtil.SHA256("123456"));
    System.out.println("SHA-384\t" + MDUtil.SHA384("123456"));
    System.out.println("SHA-512\t" + MDUtil.SHA512("123456"));
}

输入后果:

MD5      e10adc3949ba59abbe56e057f20f883e
SHA-1    7c4a8d09ca3762af61e59520943dc26494f8941b
SHA-224  f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6
SHA-256  8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
SHA-384  0a989ebc4a77b56a6e2bb7b19d995d185ce44090c13e2984b7ecc6d446d4b61ea9991b76a4c2f04b1b4d244841449454
SHA-512  ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413

我用的是 Java8,还不反对 SHA-3,所以下面代码只封装了 MD5、SHA- 1 和 SHA-2。

从 Java9 开始反对 SHA-3

6. 残缺代码

残缺代码请拜访我的 Github,若对你有帮忙,欢送给个 Star,谢谢!

https://github.com/gozhuyinglong/blog-demos/blob/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/utils/MDUtil.java

7. 举荐浏览

  • 散列表(哈希表)

退出移动版