/******************************************************************************* * Copyright (c) 2005, 2014 springside.github.io * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ package org.springside.modules.utils.text; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.zip.CRC32; import org.apache.commons.lang3.Validate; import org.springside.modules.utils.base.annotation.NotNull; import org.springside.modules.utils.base.annotation.Nullable; import com.google.common.hash.Hashing; /** * 封装各种Hash算法的工具类 * * 1.SHA-1, 安全性较高, 返回byte[](可用Encodes进一步被编码为Hex, Base64) * * 性能优化,使用ThreadLocal的MessageDigest(from ElasticSearch) * * 支持带salt并且进行迭代达到更高的安全性. * * MD5的安全性较低, 只在文件Checksum时支持. * * 2.crc32, murmur32这些不追求安全性, 性能较高, 返回int. * * 其中crc32基于JDK, murmurhash基于guava * * @author calvin */ public class HashUtil { public static final int MURMUR_SEED = 1318007700; // ThreadLocal重用MessageDigest private static ThreadLocal<MessageDigest> createThreadLocalMessageDigest(final String digest) { return new ThreadLocal<MessageDigest>() { @Override protected MessageDigest initialValue() { try { return MessageDigest.getInstance(digest); } catch (NoSuchAlgorithmException e) { throw new RuntimeException( "unexpected exception creating MessageDigest instance for [" + digest + "]", e); } } }; } private static final ThreadLocal<MessageDigest> MD5_DIGEST = createThreadLocalMessageDigest("MD5"); private static final ThreadLocal<MessageDigest> SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1"); private static SecureRandom random = new SecureRandom(); ////////////////// SHA1 /////////////////// /** * 对输入字符串进行sha1散列. */ public static byte[] sha1(@NotNull byte[] input) { return digest(input, get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列, 编码默认为UTF8. */ public static byte[] sha1(@NotNull String input) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt) { return digest(input, get(SHA_1_DIGEST), salt, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(@NotNull String input, @Nullable byte[] salt) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, 1); } /** * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. * * @see #generateSalt(int) */ public static byte[] sha1(@NotNull byte[] input, @Nullable byte[] salt, int iterations) { return digest(input, get(SHA_1_DIGEST), salt, iterations); } /** * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. * * @see #generateSalt(int) */ public static byte[] sha1(@NotNull String input, @Nullable byte[] salt, int iterations) { return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, iterations); } private static MessageDigest get(ThreadLocal<MessageDigest> messageDigest) { MessageDigest instance = messageDigest.get(); instance.reset(); return instance; } /** * 对字符串进行散列, 支持md5与sha1算法. */ private static byte[] digest(@NotNull byte[] input, MessageDigest digest, byte[] salt, int iterations) { // 带盐 if (salt != null) { digest.update(salt); } // 第一次散列 byte[] result = digest.digest(input); // 如果迭代次数>1,进一步迭代散列 for (int i = 1; i < iterations; i++) { digest.reset(); result = digest.digest(result); } return result; } /** * 用SecureRandom生成随机的byte[]作为salt. * * @param numBytes salt数组的大小 */ public static byte[] generateSalt(int numBytes) { Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", numBytes); byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); return bytes; } /** * 对文件进行sha1散列. */ public static byte[] sha1File(InputStream input) throws IOException { return digestFile(input, get(SHA_1_DIGEST)); } /** * 对文件进行md5散列,被破解后MD5已较少人用. */ public static byte[] md5File(InputStream input) throws IOException { return digestFile(input, get(MD5_DIGEST)); } private static byte[] digestFile(InputStream input, MessageDigest messageDigest) throws IOException { int bufferLength = 8 * 1024; byte[] buffer = new byte[bufferLength]; int read = input.read(buffer, 0, bufferLength); while (read > -1) { messageDigest.update(buffer, 0, read); read = input.read(buffer, 0, bufferLength); } return messageDigest.digest(); } ////////////////// 基于JDK的CRC32 /////////////////// /** * 对输入字符串进行crc32散列返回int, 返回值有可能是负数. * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static int crc32AsInt(@NotNull String input) { return crc32AsInt(input.getBytes(Charsets.UTF_8)); } /** * 对输入字符串进行crc32散列返回int, 返回值有可能是负数. * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static int crc32AsInt(@NotNull byte[] input) { CRC32 crc32 = new CRC32(); crc32.update(input); // CRC32 只是 32bit int,为了CheckSum接口强转成long,此处再次转回来 return (int) crc32.getValue(); } /** * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static long crc32AsLong(@NotNull String input) { return crc32AsLong(input.getBytes(Charsets.UTF_8)); } /** * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long * * Guava也有crc32实现, 但返回值无法返回long,所以统一使用JDK默认实现 */ public static long crc32AsLong(@NotNull byte[] input) { CRC32 crc32 = new CRC32(); crc32.update(input); return crc32.getValue(); } ////////////////// 基于Guava的MurMurHash /////////////////// /** * 对输入字符串进行murmur32散列, 返回值可能是负数 */ public static int murmur32AsInt(@NotNull byte[] input) { return Hashing.murmur3_32(MURMUR_SEED).hashBytes(input).asInt(); } /** * 对输入字符串进行murmur32散列, 返回值可能是负数 */ public static int murmur32AsInt(@NotNull String input) { return Hashing.murmur3_32(MURMUR_SEED).hashString(input, Charsets.UTF_8).asInt(); } /** * 对输入字符串进行murmur128散列, 返回值可能是负数 */ public static long murmur128AsLong(@NotNull byte[] input) { return Hashing.murmur3_128(MURMUR_SEED).hashBytes(input).asLong(); } /** * 对输入字符串进行murmur128散列, 返回值可能是负数 */ public static long murmur128AsLong(@NotNull String input) { return Hashing.murmur3_128(MURMUR_SEED).hashString(input, Charsets.UTF_8).asLong(); } }