数据库 - 密码保护

时间:2017-04-28 18:24:49

标签: java mysql sql

我有一个注册表,当我将其保存到我的数据库时,我可以看到我的密码以明文形式有任何方法用星号或圆圈替换它,以便没有人能够阅读。 我不希望任何加密只是简单的替换星号

enter image description here

1 个答案:

答案 0 :(得分:0)

如前所述,密码应始终加密,以便永远不会建立其真实值(就我而言,这应该是法律)。甚至管理员,开发人员等都不应该能够建立密码的真正价值。将加密与另一个确定正确密码的加密进行比较。一对真正好的 密码 加密机制是 BCrypt (或 jBCrypt )和 Argon2 (或 Argon2i )。 PBKDF2 也很不错,但不像前面提到的那样安全,至少从我读过的内容来看。

让我们使用jBCrypt加密以及我们将创建的SQLite数据库做一个例子:

首先你需要download the jBCrypt源文件并创建jBCrypt库(这实际上是BCrypt,其中有一些简单的编码):

在您喜欢的Java IDE中创建一个新的Library项目,并将其命名为 jBCrypt ; 将新包添加到名为 org.mindrot.jbcrypt 的jBCrypt项目中; 在您的软件包中添加一个新的JAva类并将其命名为 BCrypt ; 将源代码复制/粘贴到新类中(在包org.mindrot.jbcrypt; 行下); 构建JAR文件; 将构建的jar文件复制/粘贴到保存所有Java库的位置;

现在您需要下载SQLite JDBC Driver,以便我们可以创建和访问SQLite数据库。我相信最新版本是 sqlite-jdbc-3.16.1.jar 。将此JAR文件存储在保存所有Java库的位置。

接下来,您需要在您喜欢的Java IDE中创建一个新的 Java Application ,并将其命名为:登录。将BCrypt jar和SQLite Driver jar添加到项目中,然后将以下可运行代码复制/粘贴到其中:

package login;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.SwingConstants;
import org.mindrot.jbcrypt.BCrypt;
import static org.mindrot.jbcrypt.BCrypt.hashpw;

/**
 *
 * @author DevilsHnd
 */
public class Login {
    // Create a dummy JFrame. This is done so that
    // the Dialog boxes that are used in the application
    // do not hide behind the IDE. Not a problem for
    // some.
    private static final JFrame frame = new JFrame();

    // Define the BCrypt workload to use when generating
    // password hashes. 10-31 is a valid value.
    private static final int workload = 12;

    /**
     * A simple test case for the main method, verify that a pre-generated test
     * hash verifies successfully for the password it represents, and also
     * generate a new hash and ensure that the new hash verifies just the same.
     * @param args
     */
    @SuppressWarnings("UnusedAssignment")
    public static void main(String[] args) {
        //Set Appplication Look&Feel
        setLookAndFeel();

        // Build an small example SQLite database.
        if (!buildSQLiteDatabase("ApplicationDatabase", "Users")) {
            System.out.println("Can't build SQLite database!");
            System.exit(0);
        }

        // Set a loop so you can play with this for a
        // while with differnt User Names and Passwords.
        while (true) {
            String uName = "";
            String passWrd = "";
            // Get User input as to User Name and Password.
            // using a custom Input Dialog...
            // Build the dialog Panel...
            BorderLayout layout = new BorderLayout();
            JPanel panel = new JPanel(layout);
            JLabel label = new JLabel("<html>Please supply the specified Login information<br>"
                                + "to add a new User and Password to the database<br>"
                                + "or select the User Name you want to test the<br>"
                                + "Password for:<br><br>");
            panel.add(label, BorderLayout.NORTH);
            JPanel p = new JPanel(new BorderLayout(5,5));
            JPanel labels = new JPanel(new GridLayout(0,1,2,2));
            labels.add(new JLabel("User Name:", SwingConstants.RIGHT));
            labels.add(new JLabel("Password:", SwingConstants.RIGHT));
            p.add(labels, BorderLayout.WEST);
            JPanel controls = new JPanel(new GridLayout(0,1,2,2));

            String[] choices = fillComboFromDB("ApplicationDatabase", "Users");
            JComboBox userName = new JComboBox(choices);
            userName.setEditable(true);
            userName.setSelectedItem("");

            //JTextField userName = new JTextField();
            controls.add(userName);
            JPasswordField password = new JPasswordField();
            controls.add(password);
            p.add(controls, BorderLayout.CENTER);
            panel.add(p);
            JLabel baseLabel = new JLabel("<html><pre><font size=2>                  "
                    + "Select <font color=red>Cancel</font> to quit</font></pre></html>");
            panel.add(baseLabel, BorderLayout.SOUTH);

            // Get Input from User...
            int res = JOptionPane.showConfirmDialog(frame, panel, "User Login ...",
                    JOptionPane.OK_CANCEL_OPTION);

            // Process the result from our custom Input Dialog
            boolean itemIsSelected = false;
            if (res == JOptionPane.OK_OPTION && !userName.getSelectedItem().toString().equals("") &&
                    !Arrays.toString(password.getPassword()).equals("")) {
                if (userName.getSelectedIndex() != -1) { itemIsSelected = true; }
                uName = userName.getSelectedItem().toString();
                char[] pass = password.getPassword();
                for (int i = 0; i < pass.length; i++) {
                    passWrd+= Character.toString(pass[i]);
                }
            }
            else {
                //dialog was canceled...exit loop
                break;
            }

            if (!itemIsSelected) {
                //Encrypt the supplied Password for the supplied User.
                String salt = BCrypt.gensalt(workload);
                String encryptedPassword = hashpw(passWrd, salt);

                // Save User Name and Password into database
                if (!storeUserPass("ApplicationDatabase", "Users", uName, encryptedPassword)) {
                    System.out.println("Can Not Store User Data Into Database!");
                    continue;
                }

                // Inform of database entry
                JOptionPane.showMessageDialog(frame,"The supplied User Name and encrypted "
                        + "Password has\nbeen stored into database!", "User Saved To "
                        + "Database.\n\nEcrypted Password is:\n" + encryptedPassword,
                        JOptionPane.INFORMATION_MESSAGE);
                        continue;
            }

            // Get User Name and Password from database...
            String storedPassword = getPasswordFromDB("ApplicationDatabase", "Users", uName);
            // Is it a valid hash?
            if (!storedPassword.startsWith("$2a$")) {
                JOptionPane.showMessageDialog(frame,"The encrypted password stored in database does not\n"
                        + "appear to contain a valid hash!", "Invalid Password Hash!",
                        JOptionPane.WARNING_MESSAGE);
                break;
            }
            System.out.println("Stored Password is: " + storedPassword);

            // Compare the supplied password with what is in
            // the database for supplied User...
            if (hashpw(passWrd, storedPassword).equals(storedPassword)) {
                JOptionPane.showMessageDialog(frame,"Your Password Is VALID! You're Good To Go!",
                        "Valid Password!",JOptionPane.INFORMATION_MESSAGE);
            }
            else {
                JOptionPane.showMessageDialog(frame,"The Password Supplied Is INVALID!",
                        "Valid Password!",JOptionPane.ERROR_MESSAGE);
            }

            // Try another entry?
            res = JOptionPane.showConfirmDialog(frame,"Do you want to try another User Name or\n"
                    + "Change the password of a current User in DB?",
                      "Try Another User?",JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);
            if (res == JOptionPane.NO_OPTION) { break; }
        }
        System.exit(0); // Close application
    }

    /**
     * This method can be used to generate a string representing an account
     * password suitable for storing in a database. It will be an OpenBSD-style
     * crypt(3) formatted hash string of length=60 The bcrypt workload is
     * specified in the above static variable, a value from 10 to 31. A workload
     * of 12 is a very reasonably safe default. This automatically handles secure
     * 128-bit salt generation and storage within the hash.
     *
     * @param password_plaintext The account's plaintext password as provided
     * during account creation, or when changing an account's password.
     *
     * @return String - a string of length 60 that is the bcrypt hashed password
     * in crypt(3) format.
     */
    /*
    public static String hashPassword(String password_plaintext) {
        String salt = BCrypt.gensalt(workload);
        String hashed_password = hashpw(password_plaintext, salt);
        return (hashed_password);
    }
    */

    /**
     * This method can be used to verify a computed hash from a plaintext (e.g.
     * during a login request) with that of a stored hash from a database. The
     * password hash from the database must be passed as the second variable.
     *
     * @param passwordAsPlainText The accounts plaintext password, as provided
     * during a login request
     *
     * @param storedEncryption The accounts stored password hash, retrieved from the
     * authorization database
     *
     * @return boolean - true if the password matches the password of the stored
     * hash, false otherwise
     */
    /*
    public static boolean checkPassword(String passwordAsPlainText, String storedEncryption) {
        boolean password_verification;

        if (null == storedEncryption || !storedEncryption.startsWith("$2a$")) {
            throw new java.lang.IllegalArgumentException(
                    "Invalid encryption provided for comparison");
        }

        password_verification = checkpw(passwordAsPlainText, storedEncryption);

        return (password_verification);
    }
    */

    /**
     * This will build a SQLite database (only if it doesn't already exist) named
     * ApplicationDatabase.sqlite. It also builds a database Table name Users which
     * consists of a ID column (Integer - Auto increment - Primary Key), UserName
     * column (Text - Null not allowed), and a Password column (Text - Null not
     * Allowed).
     *
     * @param databaseName (String) The database name to use. If the .sqlite file
     * extention is not supplied with the name then it is automatically appended.
     *
     * @param tableName (String) The Table name to create.
     *
     * @return (Boolean) True if successful and false if not.
     */
    @SuppressWarnings("null")
    public static boolean buildSQLiteDatabase(String databaseName, String tableName) {
        if (!databaseName.endsWith("sqlite")) { databaseName+= ".sqlite"; }

        //Skip building DB if it already Exists.
        File file = new File(databaseName);
        if (file.exists()) { return true; }

        Connection conn = null;
        Statement stmt = null;
        try {
            Class.forName("org.sqlite.JDBC");
            String url = "jdbc:sqlite:" + databaseName;
            //create the DB
            conn = DriverManager.getConnection(url);

            // Create the table in DB..
            stmt = conn.createStatement();
            String sql = "CREATE TABLE " + tableName + " ("
                    + "ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
                    + "UserName TEXT NOT NULL,"
                    + "Password TEXT NOT NULL);";
            stmt.executeUpdate(sql);
            return true;
        }
        catch (ClassNotFoundException | SQLException ex) {
            ex.printStackTrace();
            return false;
        }
        finally {
            try { if (conn != null) { stmt.close(); conn.close(); } }
            catch (SQLException ex) { ex.printStackTrace(); }
        }
    }

    public static String[] fillComboFromDB(String databaseName, String tableName) {
        if (!databaseName.endsWith("sqlite")) { databaseName+= ".sqlite"; }
        String sql;

        Connection conn;
        PreparedStatement stmt;

        int count = 0;
        try {
            Class.forName("org.sqlite.JDBC");
            String url = "jdbc:sqlite:" + databaseName;
            //create the DB
            conn = DriverManager.getConnection(url);

            // See if User Name already exists in database.
            // If it does then use the sql UPDATE statement
            // instead of the INSERT INTO statement.
            sql = "SELECT COUNT(*) AS rCount FROM " + tableName +";";
            stmt = conn.prepareStatement(sql);
            try (ResultSet rs = stmt.executeQuery()) {
                while (rs.next()) { count = rs.getInt("rCount"); }
            }
            String[] returnItems =  new String[count];
            sql = "SELECT UserName FROM " + tableName +";";
            stmt = conn.prepareStatement(sql);
            try (ResultSet rs = stmt.executeQuery()) {
                count = 0;
                while (rs.next()) {
                    returnItems[count] = rs.getString("UserName");
                    count++;
                }
                rs.close();
                stmt.close();
            }
            conn.close();
            return returnItems;
        }
        catch (ClassNotFoundException | SQLException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * This will either: <pre>
     *
     *      - Create a new record within the supplied table if the supplied User
     *        Name does not exist or;
     *
     *      - It will Update the record (password in particular) for a User that
     *        already exists within the database table. This is based on the User
     *        Name supplied.
     * </pre>
     *
     * @param databaseName (String) The database name to use. If the .sqlite file
     * extention is not supplied with the name then it is automatically appended.
     *
     * @param tableName (String) The Table name to access.
     *
     * @param name (String) User Name to either store or modify password for.
     *
     * @param pass (String) The Password to store.
     *
     * @return (Boolean) True if successful and false if not.
     */
    @SuppressWarnings("null")
    public static boolean storeUserPass(String databaseName, String tableName,
                    String name, String pass) {
        if (!databaseName.endsWith("sqlite")) { databaseName+= ".sqlite"; }
        String sql;

        Connection conn;
        PreparedStatement stmt;

        int count = 0;
        try {
            Class.forName("org.sqlite.JDBC");
            String url = "jdbc:sqlite:" + databaseName;
            //create the DB
            conn = DriverManager.getConnection(url);

            // See if User Name already exists in database.
            // If it does then use the sql UPDATE statement
            // instead of the INSERT INTO statement.
            sql = "SELECT COUNT(*) AS rCount FROM " + tableName +
                    " WHERE UserName = '" + name + "';";
            stmt = conn.prepareStatement(sql);
            try (ResultSet rs = stmt.executeQuery()) {
                while (rs.next()) { count = rs.getInt("rCount"); }
                rs.close();
                stmt.close();
            }

            if (count > 0) {
                sql = "UPDATE " + tableName + " SET Password = '" + pass +
                        "' WHERE UserName = '" + name + "';";
            }
            else {
                sql = "INSERT INTO " + tableName + "(UserName, Password) "
                   + "VALUES ('" + name + "','"+ pass + "');";
            }
            try (
                //Modify data in table or insert data into table
                Statement smt = conn.createStatement()) {
                smt.execute(sql);
                smt.close();
            }
            conn.close();
            return true;
        }
        catch (ClassNotFoundException | SQLException ex) {
            ex.printStackTrace();
            return false;
        }
    }

    /**
     * This will retrieve the encrypted password from database for the supplied
     * User Name.
     *
     * @param databaseName (String) The database name to use. If the .sqlite file
     * extention is not supplied with the name then it is automatically appended.
     *
     * @param tableName (String) The Table name to access.
     *
     * @param userName (String) User Name to retrieve password for.
     *
     * @return (String) the Encrypted password.
     */
    public static String getPasswordFromDB(String databaseName,
                            String tableName, String userName) {
        if (!databaseName.endsWith("sqlite")) { databaseName+= ".sqlite"; }
        String sql = "SELECT Password FROM Users WHERE UserName = '" + userName + "';";
        Connection conn = null;
        try {
            Class.forName("org.sqlite.JDBC");
            String url = "jdbc:sqlite:" + databaseName;
            //create the DB
            conn = DriverManager.getConnection(url);

            String p;
            try (PreparedStatement stmt = conn.prepareStatement(sql);
                    ResultSet rs = stmt.executeQuery()) {
                p = "";
                while (rs.next()) {
                    p = rs.getString("Password");
                }
            }
            conn.close();
            return p;
        }
        catch (ClassNotFoundException | SQLException ex) {
            ex.printStackTrace();
            return "";
        }
        finally {
            try { if (conn != null) { conn.close(); } }
            catch (SQLException ex) { ex.printStackTrace(); }
        }
    }

    //Set the application's Look & Feel.
    private static void setLookAndFeel() {
        frame.setAlwaysOnTop(true);
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                //Nimbus
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                | javax.swing.UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        }
    }
}