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

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无法识别被截断的字符串中的有效盐格式,因此抛出「无效盐版本」错误。

另外,登录逻辑中存在冗余操作:对用户输入的明文密码再次哈希,虽然这不是触发当前错误的直接原因,但会导致校验逻辑错误(用哈希后的密码去比对数据库哈希值)。

解决方案

  1. 修改数据库字段长度
    执行SQL语句,将user_info表的password字段修改为足够长度:

    ALTER TABLE user_info MODIFY COLUMN password VARCHAR(255) NOT NULL;
    

    (BCrypt哈希值固定60字符,设置255留足冗余,避免后续格式变化导致的截断)

  2. 修复登录逻辑中的冗余哈希
    删除登录代码中这行冗余的哈希操作:

    // 移除这行代码
    password2 = BCrypt.hashpw(password2, BCrypt.gensalt());
    
  3. 增强登录逻辑的健壮性
    增加结果集为空的判断,避免用户名不存在时触发ResultSet空指针异常:

    // 在resultSet.next()前增加判断
    if(!resultSet.next()){
        JOptionPane.showMessageDialog(frame, "用户名不存在");
        preparedStatement.close();
        connection.close();
        return;
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:03:11