import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** * 企业级SHA哈希工具类 * 支持SHA-256/SHA-512、盐值哈希、HMAC、流式处理等高级特性 * * @author Kent * @version 2.0 */ public final class KentSha { // ==================== 常量定义 ==================== private static final String SHA_256 = "SHA-256"; private static final String SHA_512 = "SHA-512"; private static final String HMAC_SHA_256 = "HmacSHA256"; private static final String HMAC_SHA_512 = "HmacSHA512"; private static final int SALT_LENGTH = 16; // 盐值长度(字节) private static final int STREAM_BUFFER_SIZE = 8192; // 流处理缓冲区大小 // 十六进制字符查表,避免重复创建 private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); // 线程安全的MessageDigest缓存 private static final Map DIGEST_CACHE = new ConcurrentHashMap<>(); // 安全随机数生成器(单例) private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // ==================== 核心哈希方法 ==================== /** * 计算字符串的SHA-256哈希值 * @param input 输入字符串,不能为null * @return SHA-256哈希值的十六进制字符串 * @throws IllegalArgumentException 如果输入为null * @throws CryptoException 如果哈希计算失败 */ public String sha256(final String input) { return computeHash(input, SHA_256); } /** * 计算字符串的SHA-512哈希值 * @param input 输入字符串,不能为null * @return SHA-512哈希值的十六进制字符串 * @throws IllegalArgumentException 如果输入为null * @throws CryptoException 如果哈希计算失败 */ public String sha512(final String input) { return computeHash(input, SHA_512); } /** * 通用哈希计算方法(线程安全) */ private String computeHash(final String input, final String algorithm) { validateInput(input); if (input.isEmpty()) { return getEmptyHash(algorithm); } try { MessageDigest digest = getMessageDigest(algorithm); byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); } catch (Exception e) { throw new CryptoException("哈希计算失败: " + algorithm, e); } } // ==================== 盐值哈希方法 ==================== /** * 使用固定盐值计算SHA-256哈希 * @param input 输入字符串 * @param salt 盐值 * @return 加盐后的哈希值 */ public String sha256WithSalt(final String input, final String salt) { validateInput(input); validateInput(salt); return sha256(input + salt); } /** * 使用随机盐值计算SHA-256哈希 * @param input 输入字符串 * @return 包含哈希值和盐值的结果对象 */ public SaltedHashResult sha256WithRandomSalt(final String input) { validateInput(input); String salt = generateRandomSalt(); String hash = sha256WithSalt(input, salt); return new SaltedHashResult(hash, salt); } /** * 验证加盐哈希 */ public boolean verifySaltedHash(String input, String storedHash, String salt) { if (input == null || storedHash == null || salt == null) { return false; } String computedHash = sha256WithSalt(input, salt); return constantTimeEquals(computedHash, storedHash); } // ==================== HMAC方法 ==================== /** * 计算HMAC-SHA256 * @param input 输入数据 * @param secretKey 密钥 * @return HMAC哈希值 */ public String hmac256(final String input, final String secretKey) { validateInput(input); validateInput(secretKey); try { Mac mac = Mac.getInstance(HMAC_SHA_256); SecretKeySpec keySpec = new SecretKeySpec( secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA_256); mac.init(keySpec); byte[] hash = mac.doFinal(input.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); } catch (Exception e) { throw new CryptoException("HMAC计算失败", e); } } /** * 计算HMAC-SHA512 */ public String hmac512(final String input, final String secretKey) { validateInput(input); validateInput(secretKey); try { Mac mac = Mac.getInstance(HMAC_SHA_512); SecretKeySpec keySpec = new SecretKeySpec( secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA_512); mac.init(keySpec); byte[] hash = mac.doFinal(input.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); } catch (Exception e) { throw new CryptoException("HMAC计算失败", e); } } // ==================== 流式处理方法 ==================== /** * 计算输入流的SHA-256哈希(适用于大文件) * @param inputStream 输入流 * @return 哈希值 * @throws IOException 如果读取流失败 */ public String sha256Stream(final InputStream inputStream) throws IOException { Objects.requireNonNull(inputStream, "输入流不能为null"); return computeStreamHash(inputStream, SHA_256); } /** * 计算输入流的SHA-512哈希 */ public String sha512Stream(final InputStream inputStream) throws IOException { Objects.requireNonNull(inputStream, "输入流不能为null"); return computeStreamHash(inputStream, SHA_512); } private String computeStreamHash(final InputStream inputStream, final String algorithm) throws IOException { try { MessageDigest digest = getMessageDigest(algorithm); byte[] buffer = new byte[STREAM_BUFFER_SIZE]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { digest.update(buffer, 0, bytesRead); } return bytesToHex(digest.digest()); } catch (IOException e) { throw e; // 重新抛出IO异常 } catch (Exception e) { throw new CryptoException("流哈希计算失败: " + algorithm, e); } } // ==================== 批量处理方法 ==================== /** * 批量计算多个字符串的哈希值 * @param inputs 输入字符串数组 * @param algorithm 算法名称 * @return 哈希值数组 */ public String[] batchHash(final String[] inputs, final String algorithm) { if (inputs == null) { return new String[0]; } String[] results = new String[inputs.length]; for (int i = 0; i < inputs.length; i++) { try { results[i] = computeHash(inputs[i], algorithm); } catch (CryptoException e) { results[i] = null; // 单个失败不影响其他 } } return results; } // ==================== 工具方法 ==================== /** * 验证两个哈希值是否相等(恒定时间比较,防止时序攻击) */ public boolean constantTimeEquals(String hash1, String hash2) { if (hash1 == null || hash2 == null) { return false; } if (hash1.length() != hash2.length()) { return false; } int result = 0; for (int i = 0; i < hash1.length(); i++) { result |= hash1.charAt(i) ^ hash2.charAt(i); } return result == 0; } /** * 生成随机盐值 */ public String generateRandomSalt() { byte[] saltBytes = new byte[SALT_LENGTH]; SECURE_RANDOM.nextBytes(saltBytes); return bytesToHex(saltBytes); } /** * 获取支持的算法列表 */ public Set getSupportedAlgorithms() { Set algorithms = new HashSet<>(); try { for (String algorithm : Arrays.asList(SHA_256, SHA_512)) { MessageDigest.getInstance(algorithm); algorithms.add(algorithm); } } catch (NoSuchAlgorithmException e) { // 忽略不支持的算法 } return algorithms; } // ==================== 私有辅助方法 ==================== private void validateInput(final String input) { Objects.requireNonNull(input, "输入参数不能为null"); } private MessageDigest getMessageDigest(String algorithm) { return DIGEST_CACHE.computeIfAbsent(algorithm, algo -> { try { return MessageDigest.getInstance(algo); } catch (NoSuchAlgorithmException e) { throw new CryptoException("不支持的算法: " + algo, e); } }); } private String getEmptyHash(String algorithm) { // 预计算的空字符串哈希值,避免重复计算 if (SHA_256.equals(algorithm)) { return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; } else if (SHA_512.equals(algorithm)) { return "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; } return computeHash("", algorithm); } private String bytesToHex(byte[] bytes) { StringBuilder hexString = new StringBuilder(bytes.length * 2); for (byte b : bytes) { hexString.append(HEX_ARRAY[b >>> 4 & 0xF]); hexString.append(HEX_ARRAY[b & 0xF]); } return hexString.toString(); } // ==================== 内部类 ==================== /** * 加盐哈希结果封装类 */ public static class SaltedHashResult { private final String hash; private final String salt; public SaltedHashResult(String hash, String salt) { this.hash = Objects.requireNonNull(hash, "哈希值不能为null"); this.salt = Objects.requireNonNull(salt, "盐值不能为null"); } public String getHash() { return hash; } public String getSalt() { return salt; } @Override public String toString() { return String.format("SaltedHashResult{hash='%s', salt='%s'}", hash.substring(0, Math.min(8, hash.length())) + "...", salt.substring(0, Math.min(8, salt.length())) + "..."); } } /** * 自定义加密异常 */ public static class CryptoException extends RuntimeException { public CryptoException(String message) { super(message); } public CryptoException(String message, Throwable cause) { super(message, cause); } } }