Java中验证SHA-256加盐哈希后用户密码的实现方法
问题根因
你的比对逻辑出错的核心原因是:加盐哈希的逻辑中,同一个密码必须搭配同一个盐才能得到相同的哈希结果。你在生成存储用的哈希时生成了一次随机盐,校验的时候又调用getSalt()重新生成了新的随机盐,两次盐不一样,自然哈希结果永远不匹配。
正确的实现逻辑
整个密码存储校验流程应该遵循以下步骤:
- 用户注册/设置密码时:
- 生成随机盐
- 用盐+原始密码计算哈希值
- 把盐和哈希值一起存入数据库,盐不需要加密,明文存储即可
- 用户登录校验时:
- 先根据用户名从数据库取出对应存储的盐和哈希值
- 用取出的盐+用户提交的登录密码计算新的哈希值
- 比对新计算的哈希值和数据库存储的哈希值是否一致,一致则校验通过
修正后的代码示例
你可以根据自己需要调整哈希算法为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
相关产品推荐
相关产品推荐

