Why does JPasswordField.getPassword() create a String with the password in it?
Solution 1:
This works for me and helps you to build a Stringified password:
String passText = new String(passField.getPassword());
Solution 2:
Actually, here's the Sun implementation of getPassword()
:
public char[] getPassword() {
Document doc = getDocument();
Segment txt = new Segment();
try {
doc.getText(0, doc.getLength(), txt); // use the non-String API
} catch (BadLocationException e) {
return null;
}
char[] retValue = new char[txt.count];
System.arraycopy(txt.array, txt.offset, retValue, 0, txt.count);
return retValue;
}
The only getText
in there is a call to getText(int offset, int length, Segment txt)
, which calls getChars(int where, int len, Segment txt)
, which in turn copies characters directly into the Segment
's buffer. There are no Strings
being created there.
Then, the Segment
's buffer is copied into the return value and zeroed out before the method returns.
In other words: There is no extra copy of the password hanging around anywhere. It's perfectly safe as long as you use it as directed.
Solution 3:
Ok, my bad... All the bells started ringing as soon as I saw the call to getText() without noticing that it was actually introduced by me with the Action listener here's a stacktrace
PasswordTest$1.getText() line: 14
PasswordTest$1(JTextField).fireActionPerformed() line: not available
PasswordTest$1(JTextField).postActionEvent() line: not available
JTextField$NotifyAction.actionPerformed(ActionEvent) line: not available
SwingUtilities.notifyAction(Action, KeyStroke, KeyEvent, Object, int) line: not available
Here is the code used:
import java.awt.event.*;
import javax.swing.*;
public class PasswordTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPasswordField passField = new JPasswordField() {
@Override
public String getText() {
System.err.println("Awhooa: " + super.getText()); //breakpoint
return null;
}
};
passField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
char[] p = passField.getPassword();
System.out.println(p);
}
});
frame.add(passField);
frame.setVisible(true);
frame.pack();
}
}
And here is the console output:
Awhooa: secret
secret
And for the actual call to getPassword(), maybe I am missing something, but where is Segment's buffer zeroed? I see an array copy, but not a zeroing. The returned array will be zeroed by myself, but Segment's array is still there...
import java.util.Arrays;
public class ZeroingTest {
public static void main(String[] args) {
char[] a = {'a','b','c'};
char[] b = new char[a.length];
System.arraycopy(a, 0, b, 0, b.length);
System.out.println("Before zeroing: " + Arrays.toString(a) + " " + Arrays.toString(b));
Arrays.fill(a, '\0');
System.out.println("After zeroing: " + Arrays.toString(a) + " " + Arrays.toString(b));
}
}
And the output:
Before zeroing: [a, b, c] [a, b, c]
After zeroing: [?, ?, ?] [a, b, c]
(I put question marks there because I cannot past unprintable characters)
-M
Solution 4:
The Swing implementation is too complex to check by hand. You want tests.
public class Pwd {
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new javax.swing.JFrame("Pwd") {{
add(new javax.swing.JPasswordField() {
@Override public String getText() {
System.err.println("Awoooga!!");
return super.getText();
}
{
addActionListener(
new java.awt.event.ActionListener() {
public void actionPerformed(
java.awt.event.ActionEvent event
) {
// Nice.
}
}
);
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setVisible(true);
}};
}
});
}
}
Looks like the command string for the (pointless) action event to me. There will be other way to cause the effect as well.
A vaguely modern VM will move objects in memory anyway, so clearing the char[]
does not necessarily work.