答案 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();
}
}
}