DigitalFingerprint Best Practise
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 许可协议,转载请注明出处!
评论