DigitalFingerprint Best Practise

May 31, 2023 / Administrator / 38阅读 / 0评论/ 分类: Crypto

DigitalFingerprint

当我们遇到一个字符串集合时,如果想用一个唯一id,来表示这个字符串集合,应该怎么表示呢?

这里提供一个解决方案,通过加密算法,得到一个固定位数的摘要值,从而将这个摘要值,作为唯一id。

得到的摘要值,虽然可能存在相同的,但是一般来说,碰撞概率非常低,所以,可以简单的认为值是唯一的。


package com.iot.common.utils.finger;

import com.iot.common.utils.snowflake.SnowFlakeHelper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.codec.Hex;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.TreeSet;

/**
 * 数字指纹工具类
 */
public class DigitalFingerprintUtils {

    private static String ALGORITHM = "SHA-256";

    private static final Logger LOGGER = LoggerFactory.getLogger(DigitalFingerprintUtils.class);

    /**
     * 给这个data数据集合,生成数字指纹
     *
     * @param data
     * @return
     */
    public static String generateFingerprint(TreeSet<String> data) {
        try {
            MessageDigest md = MessageDigest.getInstance(ALGORITHM);
            // org.apache.commons.io.IOUtils.toByteArray(java.io.Reader, java.nio.charset.Charset)
            String joinedString = String.join(System.lineSeparator(), data);
            InputStream is = IOUtils.toInputStream(joinedString, StandardCharsets.UTF_8);
            byte[] bytes = IOUtils.toByteArray(is);
            // 填充数据
            md.update(bytes);
            // 完成摘要计算
            byte[] digest = md.digest();
            // 转为16进制
            char[] encode = Hex.encode(digest);
            return new String(encode);
        } catch (NoSuchAlgorithmException | IOException e) {
            LOGGER.error("generateFingerprint occue exception:{}", ExceptionUtils.getStackTrace(e));
            return SnowFlakeHelper.assignDistributeIdStr();
        }
    }

}


上面的代码中,有一个问题:加密算法计算后,得到一个byte数组,为什么要转为16进制的数字呢?直接将byte数组转为String字符串,有什么问题吗?


很简单因为, MD5, SHA-256, SHA-512 等等算法,它们是通过对byte[] 进行各种变换和运算,得到加密之后的byte[],那么这个加密之后的 byte[] 结果显然 就不会符合任何一种的编码方案,比如 utf-8, GBK等,因为加密的过程是任意对byte[]进行运算的。所以你用任何一种编码方案来解码 加密之后的 byte[] 结果,得到的都会是乱码。



那么,我们该如何将加密的结果 byte[] 转换到String呢?

首先,我们要问一下,为什么要将加密得到的 byte[] 转换到 String ?

答案是因为一是要对加密的结果进行存储,比如存入数据库中,二是在单向不可逆的hash加密算法对密码加密时,我们需要判断用户登录的密码是否正确,那么就涉及到两个加密之后的byte[] 进行比较,看他们是否一致。两个 byte[] 进行比较,可以一次比较一个单字节,也可以一次比较多个字节。也可以转换成String, 然后比较两个String就行了。因为加密结果要进行存储,所以其实都是选择转换成String来进行比较的。



加密解密时,采用的byte[] 到 String 转换的方法,都是将 byte[] 二进制利用16进制的char[]来表示,每一个 byte 是8个bit,每4个bit对应一个16进制字符。所以一个 byte 对应于两个 16进制字符:


我们知道16进制表达方式是使用 0-9 abcdef 这16个数字和字母来表示 0-15 这16个数字的。而显然我们在String转化时,可以用字符 ‘0’ 来表示 数字0, 可以用 ‘1’ 来表示 1,可以用 ‘f’ 来表示15.

所以上面我们看到16进制使用 "0-9abcdef’ 16个字符来表示 0-15 这个16个数字。主要的转换过程是 public static char[] encode(byte[] data)函数:

int l = data.length; char[] out = new char[l << 1]; 这两句是初始化一个 char[] 数组,其数组的大小是 byte[] 参数大小的两倍,因为每一个byte[] 转换到到2位16进制的char[]。

MessageDigest

refer to : https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html

文章作者:Administrator

文章链接:http://localhost:8090//archives/digitalfingerprintbestpractise

版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!


评论