Java与MySQL中BCrypt哈希密码校验遇无效盐错误的解决
BCrypt校验密码时出现「无效盐版本」错误的解决方法
我使用BCrypt对密码哈希后通过setString()存入MySQL数据库,调用BCrypt.checkpw()校验时触发「无效盐版本」错误。
问题相关代码片段
String password2 = BCrypt.hashpw(newPassword, BCrypt.gensalt()); // 哈希密码 preparedStatement.setString(2, password2); // 存入数据库 BCrypt.checkpw(loginPassword, str2); // 校验:loginPassword是用户输入的明文,str2是从数据库取出的哈希值
完整登录注册系统代码
/* * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template */ package com.mycompany.computerscienceia; import org.mindrot.jbcrypt.BCrypt; /** * * @author Thoma */ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import org.mindrot.jbcrypt.BCrypt; public class hashPassword { public static void main(String[] args) { JFrame frame = new JFrame("Login and Registration System"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(600, 200); frame.setLayout(new GridLayout(4, 2)); JLabel usernameLabel = new JLabel("Username:"); JTextField usernameField = new JTextField(); JLabel passwordLabel = new JLabel("Password:"); JPasswordField passwordField = new JPasswordField(); JLabel passwordVerifyLabel = new JLabel("Verify Password (registration only:"); JPasswordField passwordVerifyField = new JPasswordField(); JButton loginButton = new JButton("Login"); JButton registerButton = new JButton("Register"); frame.add(usernameLabel); frame.add(usernameField); frame.add(passwordLabel); frame.add(passwordField); frame.add(passwordVerifyLabel); frame.add(passwordVerifyField); frame.add(loginButton); frame.add(registerButton); frame.setVisible(true); // Database connection parameters String url = "jdbc:mysql://localhost:3306/users"; String user = "root"; String password = "password"; // Event handling for login button loginButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = usernameField.getText(); String password2 = new String(passwordField.getPassword()); boolean UserNameExists = false; if (username.isEmpty() == true) { JOptionPane.showMessageDialog(frame, "username field empty!"); } else { if (password2.isEmpty() == true) { JOptionPane.showMessageDialog(frame, "password field empty!"); } else { try { Connection connection = DriverManager.getConnection(url, user, password); System.out.println("Server connected!"); // hash the password password2 = BCrypt.hashpw(password2, BCrypt.gensalt()); PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM user_info WHERE username = ?"); preparedStatement.setString(1, username); ResultSet resultSet = preparedStatement.executeQuery(); // if the username exists then we need to get the password from this resultset // we need to get the result set into an array and get the password element String data[] = new String[3]; String password3 = new String(passwordField.getPassword()); resultSet.next(); String password = resultSet.getString("password"); data[2] = password; String theDatabaseHashedPassword = data[2].toString(); System.out.println("password from database is " + theDatabaseHashedPassword); Boolean isTheSame = BCrypt.checkpw(password3,theDatabaseHashedPassword); JOptionPane.showMessageDialog(frame, isTheSame); if (isTheSame == true) { JOptionPane.showMessageDialog(frame, "Login successful"); } else { JOptionPane.showMessageDialog(frame, "Login failed. Please check your credentials."); } preparedStatement.close(); connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }); // Event handling for register button registerButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = usernameField.getText(); String password2 = new String(passwordField.getPassword()); String passwordVerify = new String(passwordVerifyField.getPassword()); boolean UserNameExists = false; if (username.isEmpty() == true) { JOptionPane.showMessageDialog(frame, "username field empty!"); } else { if (password2.isEmpty() == true) { JOptionPane.showMessageDialog(frame, "password field empty!"); } else { if (passwordVerify.isEmpty() == true){ JOptionPane.showMessageDialog(frame, "please verify password!"); } else { UserNameExists = checkUsernameUnique(user, password, url, username); System.out.println("username boolean = " + UserNameExists); if (UserNameExists == true) { JOptionPane.showMessageDialog(frame, "username already exists!"); } else { // now the password is verified and username unique we can hash the password. password2 = BCrypt.hashpw(password2, BCrypt.gensalt()); try { Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_info (username, password) VALUES (?, ?)"); preparedStatement.setString(1, username); preparedStatement.setString(2, password2); int rowsAffected = preparedStatement.executeUpdate(); if (rowsAffected > 0) { JOptionPane.showMessageDialog(frame, "Registration successful!"); } else { JOptionPane.showMessageDialog(frame, "Registration failed. Please try again."); } preparedStatement.close(); connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } } } }); } public static boolean checkUsernameUnique(String uName, String pWord, String theUrl, String theUsername) { boolean unique = false; try { Connection connection = DriverManager.getConnection(theUrl, uName, pWord); System.out.println("Server connected!"); PreparedStatement preparedStatement = connection.prepareStatement("SELECT username FROM user_info;"); ResultSet resultSet = preparedStatement.executeQuery(); ArrayList<String> allUNmanes = new ArrayList<>(); while (resultSet.next()) { for (int x = 1; x <= resultSet.getMetaData().getColumnCount(); x++) { String str = resultSet.getString(x); allUNmanes.add(str); System.out.print(str + "\t"); } System.out.println(); } System.out.println(allUNmanes.toString()); if (allUNmanes.contains(theUsername) == true) { System.out.println("username already exists"); unique = true; } preparedStatement.close(); connection.close(); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("username boolean = " + unique); return unique; } }
问题原因分析
经排查,核心原因是数据库中password字段长度不足:BCrypt生成的哈希值固定为60字符,如果数据库字段长度小于60(比如用了VARCHAR(50)),哈希值会被截断。校验时,BCrypt无法识别被截断的字符串中的有效盐格式,因此抛出「无效盐版本」错误。
另外,登录逻辑中存在冗余操作:对用户输入的明文密码再次哈希,虽然这不是触发当前错误的直接原因,但会导致校验逻辑错误(用哈希后的密码去比对数据库哈希值)。
解决方案
修改数据库字段长度
执行SQL语句,将user_info表的password字段修改为足够长度:ALTER TABLE user_info MODIFY COLUMN password VARCHAR(255) NOT NULL;(BCrypt哈希值固定60字符,设置255留足冗余,避免后续格式变化导致的截断)
修复登录逻辑中的冗余哈希
删除登录代码中这行冗余的哈希操作:// 移除这行代码 password2 = BCrypt.hashpw(password2, BCrypt.gensalt());增强登录逻辑的健壮性
增加结果集为空的判断,避免用户名不存在时触发ResultSet空指针异常:// 在resultSet.next()前增加判断 if(!resultSet.next()){ JOptionPane.showMessageDialog(frame, "用户名不存在"); preparedStatement.close(); connection.close(); return; }
内容的提问来源于stack exchange,提问作者Thomas Humphreys
相关产品推荐
相关产品推荐

