企业级SHA哈希工具类

2026年01月23日 07:57

描述

支持SHA-256/SHA-512、盐值哈希、HMAC、流式处理等高级特性

标签

Java
企业级SHA哈希工具类.txt 10,699 字符 | 348 行
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<String, MessageDigest> 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<String> getSupportedAlgorithms() {
        Set<String> 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);
        }
    }
}

支持 Ctrl+A 全选,Ctrl+C 复制