You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中验证SHA-256加盐哈希后用户密码的实现方法

问题根因

你的比对逻辑出错的核心原因是:加盐哈希的逻辑中,同一个密码必须搭配同一个盐才能得到相同的哈希结果。你在生成存储用的哈希时生成了一次随机盐,校验的时候又调用getSalt()重新生成了新的随机盐,两次盐不一样,自然哈希结果永远不匹配。

正确的实现逻辑

整个密码存储校验流程应该遵循以下步骤:

  1. 用户注册/设置密码时:
    • 生成随机盐
    • 用盐+原始密码计算哈希值
    • 把盐和哈希值一起存入数据库,盐不需要加密,明文存储即可
  2. 用户登录校验时:
    • 先根据用户名从数据库取出对应存储的盐和哈希值
    • 用取出的盐+用户提交的登录密码计算新的哈希值
    • 比对新计算的哈希值和数据库存储的哈希值是否一致,一致则校验通过
修正后的代码示例

你可以根据自己需要调整哈希算法为SHA-256,以下是调整后的完整可运行代码:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Scanner;

public class PasswordDemo {
    // 这里可以修改为你需要的SHA-256
    private static final String HASH_ALGORITHM = "SHA-512";
    private static final String SALT_ALGORITHM = "SHA1PRNG";
    private static final int SALT_LENGTH = 16;
    private static Scanner scanner = new Scanner(System.in);

    // 原有哈希计算逻辑无需修改
    private static String getSecurePassword(String passwordToHash, byte[] salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
            md.update(salt);
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    // 原有盐生成逻辑无需修改
    private static byte[] getSalt() throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance(SALT_ALGORITHM);
        byte[] salt = new byte[SALT_LENGTH];
        sr.nextBytes(salt);
        return salt;
    }

    // 工具方法:字节数组转十六进制字符串,方便存入数据库
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 工具方法:十六进制字符串转回字节数组,校验时使用
    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i+1), 16));
        }
        return data;
    }

    public static void main(String[] args) {
        // 模拟注册流程:生成盐和哈希,存入数据库
        System.out.println("\n注册:请输入密码");
        String registerPassword = scanner.nextLine();
        String storedSaltHex = null;
        String storedHash = null;
        try {
            byte[] salt = getSalt();
            storedSaltHex = bytesToHex(salt); // 盐转字符串后存库
            storedHash = getSecurePassword(registerPassword, salt); // 哈希值存库
            System.out.println("注册成功,数据库存储的盐:" + storedSaltHex);
            System.out.println("数据库存储的哈希值:" + storedHash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        // 模拟登录校验流程
        System.out.println("\n登录:请输入密码");
        String loginPassword = scanner.nextLine();
        // 实际使用时这里从数据库查询存储的盐和哈希,此处用上面模拟生成的值演示
        byte[] saltFromDb = hexToBytes(storedSaltHex);
        String loginHash = getSecurePassword(loginPassword, saltFromDb);

        if (loginHash.equals(storedHash)) {
            System.out.println("密码正确,校验通过");
        } else {
            System.out.println("密码错误,校验失败");
        }
    }
}
额外优化建议
  • 普通SHA哈希抗暴力破解能力较弱,后续如果安全要求提升,可以考虑替换为PBKDF2、Argon2等慢哈希算法,更适合密码存储场景
  • 哈希比对阶段建议使用时间安全的比对方法避免时序攻击,Java中可以用MessageDigest.isEqual()方法替代普通的equals(),将两个哈希值转成字节数组后调用该方法比对即可

内容的提问来源于stack exchange,提问作者user9087451

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 09:39:01