微信搜索:码农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),相当于该消息的身份证。


单向散列函数有多种实现算法,常见的有:MD5、SHA-1、SHA-2和 SHA-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();

    }


}

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

82.

83.

84.

85.

86.

87.

88.

89.

90.

91.

92.

93.

94.

95.

96.

97.

98.

99.

100.

101.

102.

103.

104.

105.

106.

107.

108.

109.

110.

111.

112.

113.

114.

115.

116.

117.

118.

119.

120.

121.

122.

123.

124.

125.

126.

127.

128.

129.

130.

131.

132.

133.

134.

135.

136.

137.

138.

139.

140.

141.

142.

143.

144.

145.

146.

147.

148.

149.

150.

151.

152.

153.

154.

155.

156.

157.

158.

159.

160.

161.

162.

163.

164.

165.

166.

167.

168.

169.

170.

171.

172.

173.

174.

175.

176.

177.

178.

179.

180.

181.

182.

183.

下面分别使用这些算法计算“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"));

}

1.

2.

3.

4.

5.

6.

7.

8.

输出结果:


MD5      e10adc3949ba59abbe56e057f20f883e

SHA-1    7c4a8d09ca3762af61e59520943dc26494f8941b

SHA-224  f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6

SHA-256  8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

SHA-384  0a989ebc4a77b56a6e2bb7b19d995d185ce44090c13e2984b7ecc6d446d4b61ea9991b76a4c2f04b1b4d244841449454

SHA-512  ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413

1.

2.

3.

4.

5.

6.

我用的是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. 推荐阅读

 散列表(哈希表)

-----------------------------------

©著作权归作者所有:来自51CTO博客作者码农StayUp的原创作品,如需转载,请注明出处,否则将追究法律责任

一文搞懂单向散列加密:MD5、SHA-1、SHA-2、SHA-3

https://blog.51cto.com/u_1528868/4058818