meethigher-理解hash并封装hash常用工具类

一、理解

1.1 概念

哈希(Hash)是一种将任意长度的输入数据映射为固定输出长度的算法。

其特点有

  1. 定长:无论输入数据的大小,哈希函数都会产生固定长度的哈希值。
  2. 不可逆性:从哈希值无法逆向推导出原始输入数据。
  3. 雪崩效应:即使输入数据发生轻微变化,其生成的哈希值也会发生巨大变化。
  4. 唯一性:理论上,不同的输入数据应该生成不同的哈希值。但实际上,哈希函数拥有无限的输入空间,却只有有限的输出空间,这意味着哈希函数一定会产生碰撞。

哈希,hash的译文是弄乱的意思,中文润色后也叫散列。

哈希算法,本质就是一种单向散列函数。虽然存在碰撞问题,但该函数的初衷就是不可逆,故依然是单向的。

1.2 应用

哈希的应用场景有

  1. 加密:由于哈希的不可逆特性,特别适用于加密,如MD5算法。在存储用户密码时,不会将明文密码存储在数据库中,而是存储其哈希值。当用户登录时,系统将输入的密码进行哈希运算后与存储的哈希值进行比较,以确保密码的安全性。
  2. 数据校验:如校验两个文件内容是否相同。将两个文件内容作为哈希输入,获取其哈希输出值,若两个的输出值相同,则表示文件相同。同理也可用于文件分片下载后合成文件,比较合成文件,与原文件是否相同。
  3. 负载均衡:如nginx的iphash操作,可以实现相同ip的请求,固定代理到某个节点。将客户端的IP进行哈希计算,得到的哈希值与服务器个数进行取模运算,最终得到的值就是需要节点。
  4. 文件秒传技术:在文件上传过程中,如果上传的文件已经在服务器中存在,那么服务器会直接将已经存在的文件的信息返回给客户端,而不需要客户端再次上传文件,从而实现文件的秒传。比如,将文件压缩成128位的MD5哈希值,只要修改文件内容,MD5码就会改变,用MD5码来判断是否是同一文件,要根据实际情况考虑选择服务端或者客户端计算hash值。

要考虑彩虹表攻击

1.3 彩虹表问题

因为哈希算法相同的输入一定得到相同的输出,所以在某种程度上,将预先计算好的key与对应的hash value存起来作为对照表,就成了彩虹表。

那么如何抵御彩虹表呢?可以对每个key生成时额外添加随机值,这种方法称之为:加盐(salt)

因为加盐是对每个加密的内容中添加额外的随机数,以确保加密内容的更加安全,这个随机性带来更强的安全形的同时也带来了加密内容的随机性,随机到的不同的盐值,加密后的结果天差地别,所以,牢记salt是之后对该加密内容进行验证的关键所在!

meethigher-理解hash并封装hash常用工具类_第1张图片

1.4 常见hash算法及其工具类

常见的hash算法归类,散列值长度越高,安全性越高。

算法名称 散列值bit长度(byte) 备注
MD5 128(16)
SHA-1 160(20)
SHA-224 224(28)
SHA-256 256(32)
SHA-384 384(48)
SHA-512 512(64)
SHA-512/224 224(28) 由SHA-512算法生成的散列值,截取前224
SHA-512/256 256(32) 由SHA-512算法生成的散列值,截取前256
SHA3-224 224(28)
SHA3-256 256(32)
SHA3-384 384(48)
SHA3-512 512(64)

简单实现其工具类

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Set;

/**
 * 哈希工具类
 *
 * @author chenchuancheng
 * @since 2023/08/28 21:01
 */
public class HashUtils {

    private static final String MD5 = "MD5";
    private static final String SHA_1 = "SHA-1";
    private static final String SHA_224 = "SHA-224";
    private static final String SHA_256 = "SHA-256";
    private static final String SHA_384 = "SHA-384";
    private static final String SHA_512 = "SHA-512";
    private static final String SHA3_224 = "SHA3-224";
    private static final String SHA3_256 = "SHA3-256";
    private static final String SHA3_384 = "SHA3-384";
    private static final String SHA3_512 = "SHA3-512";
    private static final String SHA3_512_224 = "SHA3-512/224";
    private static final String SHA3_512_256 = "SHA3-512/256";


    /**
     * 列举出jdk可用的hash算法
     */
    private static void listJDKAvailableAlgorithms() {
        // 获取所有已注册的安全提供程序
        Provider[] providers = Security.getProviders();
        for (Provider provider : providers) {
            System.out.println("Provider: " + provider.getName());
            Set<Object> keys = provider.keySet();
            for (Object key : keys) {
                System.out.println("  Algorithm: " + provider.get(key));
            }
        }
    }

    /**
     * 摘要十六进制字符串
     *
     * @param algorithm  算法
     * @param dataToHash 需要散列的数据
     * @param salt       盐, 相当于在dataToHash后面直接追加了salt
     * @return {@link String}
     * @throws NoSuchAlgorithmException 没有这样算法异常
     */
    public static String digestHexString(String algorithm, String dataToHash, String salt) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        byte[] dataToHashBytes = dataToHash.getBytes(StandardCharsets.UTF_8);
        messageDigest.update(dataToHashBytes);
        if (!(salt == null || salt.isEmpty())) {
            byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
            //相当于在原内容dataToHashBytes上,追加了saltBytes
            messageDigest.update(saltBytes);
        }
        byte[] digest = messageDigest.digest();
        // 将哈希值转换为十六进制字符串
        StringBuilder hexStringBuilder = new StringBuilder();
        for (byte b : digest) {
//            String hexString = Integer.toHexString(b & 0xff);
//            hexStringBuilder.append(hexString);//这个做法转换出来的,比如0f,只会显示为f,并不会补0
            /**
             * 将byte转换为十六进制字符串
             * 0-结果不足指定宽度时,用0填充
             * 2-宽度为2
             * @see https://stackoverflow.com/questions/2817752/java-code-to-convert-byte-to-hexadecimal
             */
            String hexString = String.format("%02x", b);
            hexStringBuilder.append(hexString);
        }
        return hexStringBuilder.toString();
    }

    public static String digestHexStringByMD5(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(MD5, dataToHash, salt);
    }

    public static String digestHexStringByMD5(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(MD5, dataToHash, null);
    }

    public static String digestHexStringBySHA1(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA_1, dataToHash, salt);
    }

    public static String digestHexStringBySHA1(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA_1, dataToHash, null);
    }

    public static String digestHexStringBySHA224(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA_224, dataToHash, salt);
    }

    public static String digestHexStringBySHA224(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA_224, dataToHash, null);
    }


    public static String digestHexStringBySHA256(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA_256, dataToHash, salt);
    }

    public static String digestHexStringBySHA256(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA_256, dataToHash, null);
    }

    public static String digestHexStringBySHA384(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA_384, dataToHash, salt);
    }

    public static String digestHexStringBySHA384(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA_384, dataToHash, null);
    }

    public static String digestHexStringBySHA512(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA_512, dataToHash, salt);
    }

    public static String digestHexStringBySHA512(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA_512, dataToHash, null);
    }

    public static String digestHexStringBySHA3_224(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_224, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_224(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_224, dataToHash, null);
    }

    public static String digestHexStringBySHA3_256(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_256, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_256(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_256, dataToHash, null);
    }

    public static String digestHexStringBySHA3_384(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_384, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_384(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_384, dataToHash, null);
    }

    public static String digestHexStringBySHA3_512(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_512(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512, dataToHash, null);
    }

    public static String digestHexStringBySHA3_512_224(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512_224, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_512_224(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512_224, dataToHash, null);
    }

    public static String digestHexStringBySHA3_512_256(String dataToHash, String salt) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512_256, dataToHash, salt);
    }

    public static String digestHexStringBySHA3_512_256(String dataToHash) throws NoSuchAlgorithmException {
        return digestHexString(SHA3_512_256, dataToHash, null);
    }


}

二、参考

什么是哈希算法? - 知乎

什么是哈希算法? - 知乎

java - 一文搞懂单向散列加密:MD5、SHA-1、SHA-2、SHA-3 - 个人文章 - SegmentFault 思否

哈希(Hash)算法以及应用场景 - 知乎

哈希算法总结(含哈希算法工具类的封装)_猿究院杨树林的博客-CSDN博客

文件上传下载系列——如何实现文件秒传_秒传格式_夏诗曼CharmaineXia的博客-CSDN博客
tps://zhuanlan.zhihu.com/p/572831003)

哈希算法总结(含哈希算法工具类的封装)_猿究院杨树林的博客-CSDN博客

文件上传下载系列——如何实现文件秒传_秒传格式_夏诗曼CharmaineXia的博客-CSDN博客

你可能感兴趣的:(哈希算法,算法)