I'm trying to implement best security practices on my JDBC connection. I read that the best way to implement a password, is to use an array of characters, as opposed to a String, because Strings are immutable in Java.
The only problem is, I can't find any method in the Java API that supports hiding a password which is of type char[]
.
Any suggestions?
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
import javax.swing.JLabel;
public class DbConnect
{
public static void main(String args[])
{
try
{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = null;
Statement stmt = null;
String username = "";
String sPassword = "";
char[] cPassword;
JTextField usernameField = new JTextField(50);
JPasswordField passwordField = new JPasswordField(20);
JPanel loginPanel = new JPanel();
loginPanel.add(new JLabel("Username:"));
loginPanel.add(usernameField);
loginPanel.add(Box.createHorizontalStrut(15));
loginPanel.add(new JLabel("Password:"));
loginPanel.add(passwordField);
int result = JOptionPane.showConfirmDialog(null, loginPanel,
"Please Enter Username and Password", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
username = usernameField.getText();
cPassword = passwordField.getPassword();
//password = passwordField.getText();
for(int c = 0; c < cPassword.length; c++)
sPassword = sPassword + cPassword[c];
/*conn = DriverManager.getConnection("jdbc:mysql://x.x.x.x");*/
conn = DriverManager.getConnection("jdbc:mysql://x.x.x.x", username, sPassword);
System.out.print("Database is connected\n");
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT Fld1, Fld2, Fld3, Fld4 FROM db.tbl";
ResultSet rs = stmt.executeQuery(sql);
First, let's make it clear why char[]
is preferable to String
for passwords.
If you assume that a hacker can dump memory images of your JVM, or search your memory space in any other way, then they can find passwords that are out there in memory, given enough time. For this reason, it's best to keep passwords in memory for very short durations - get the password from the user, pass it to wherever needs it, and then remove it from memory.
Naively, one would do something like this:
String password = getPasswordFromUser();
usePassword(password);
password = null;
Now the password string (suppose it's "myPass"
), is ready to go to garbage collection. But it's still in memory. One never knows when it will be collected. Perhaps never, because you have enough memory for your application. And even when it is collected, there is no guarantee that the memory will be erased. This is a long time for the hacker to thoroughly search your memory space for likely passwords.
Now, you may have heard that character arrays are a good solution for this. So you replace the naive code above with:
char[] password = getPasswordFromUser(); // Now returning a character array
usePassword(password);
password = null;
Well, unfortunately, the same thing is true here. The character array ['m','y','P','a','s','s']
is still in memory, it's waiting for garbage collection, and it may never be erased.
So character arrays don't automatically guarantee better security. You actually have to do something like:
char[] password = getPasswordFromUser(); // Now returning a character array
usePassword(password);
Arrays.fill(password, '\u0000');
password = null;
Now each character from the original password has been erased and replaced with a zero. The object still waits for garbage collection, but there is no longer a meaningful password in it.
You can't do that directly to a String
because the char[]
array that implements it is private and you can't set the characters in it - it's immutable. So it has to be char[]
.
Now that we know that, the rule is clear: it has to be a single char[]
object all the way. That is, you can't convert it into a String
anywhere along the way, or clone or copy it. If you do - you lost the whole benefit.
So this piece of your code:
for(int c = 0; c < cPassword.length; c++)
sPassword = sPassword + cPassword[c];
Is actually breaking that rule. It creates a String
object in sPassword
, that contains the password and we lost our security.
The real problem here is that connecting to JDBC does not allow you to pass a password that is a char[]
. The DriverManager.getConnection()
methods expect String
, not char[]
, whether you put the password in the connection URL, or pass it as a parameter.
So in fact, if you want to keep this level of security for connecting over JDBC, you'll have to find an alternative method of authenticating against your database, that does not use the DriverManager.getConnection(String, String, String)
or DriverManager.getConnection(String)
.
I did not test it, but MySQL offers a way to authenticate using SSL with a client certificate. There may be other ways using other authentication plugins for Connector/J. But the simple way of asking the user for his username and password and using DriverManager.getConnection()
is not going to allow you to maintain password security via char[]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With