可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Swing's JPasswordField
has the getPassword()
method that returns a char array. My understanding of this is that the array can be zeroed immediately after use so that you do not have sensitive things hanging around in memory for long. The old way to retrieve the password was to use getText()
, which returns a String object, but it has been deprecated.
So, my question is why it is actually being used by Java during the retrieval process using getPassword()
??? To be clearer, I was debugging my test app for something else**, I followed the calls and bang... getText()
in JPasswordField
was called and, of course, a nice String object with my password has been created and now is hanging around the memory.
Try it for yourself:
public class PasswordTest() {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPasswordField passField = new JPasswordField();
pass.addActionListener(new ActionListener() {
public ActionPerformed(ActionEvent evt) {
char[] p = passField.getPassword(); // put breakpoint
// do something with the array
}
});
frame.add(passField);
frame.setVisible(true);
frame.pack();
}
}
Follow up question: is this 'hidden' use of getText()
dangerous in any way? Of course a dedicated attacker WILL get your password if it has compromised the system, I am talking about a less dedicated one ;)
**I came across this while I was looking for a way to actually display some sensitive data on a Swing component without using a String
object. Apparently there is no way to do it unless I am willing to rewrite part (all?) of the Swing API.. not gonna happen.
回答1:
This works for me and helps you to build a Stringified password:
String passText = new String(passField.getPassword());
回答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.
回答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
回答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.
回答5:
**I came across this while I was looking for a way to actually display some sensitive data on a Swing component without using a String object. Apparently there is no way to do it unless I am willing to rewrite part (all?) of the Swing API.. not gonna happen.
You can tell a JPasswordField
to display the characters by calling field.setEchoChar('\0')
. This retains the rest of the protection offered by JPasswordField
(no String
s, no cut/copy).
回答6:
import javax.swing.*;
public class login extends javax.swing.JFrame {
MainProg main = new MainProg();
public login() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
txtUser = new javax.swing.JTextField();
txtPassword = new javax.swing.JTextField();
jButton1 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Log In");
setBackground(new java.awt.Color(255, 204, 204));
setResizable(false);
jLabel1.setText("Username:");
jLabel2.setText("Password:");
jButton1.setBackground(new java.awt.Color(204, 204, 204));
jButton1.setText("Enter");
jButton1.setOpaque(false);
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(jButton1)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addGap(18, 18, 18)
.addComponent(txtUser, javax.swing.GroupLayout.PREFERRED_SIZE, 210, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addGap(20, 20, 20)
.addComponent(txtPassword))))
.addContainerGap(62, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(txtUser, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jButton1)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
String U = new String(this.txtUser.getText());
String P = new String(this.txtPass.gettext());
if(U.equals("Admin") && P.equals(Password))
{
JOptionPane.showMessageDialog(null,"Login successful!","Message",JOptionPane.INFORMATION_MESSAGE);
this.hide();
calculator.show();
}
else
{
JOptionPane.showMessageDialog(null,"Invalid username and password","Message",JOptionPane.ERROR_MESSAGE);
this.txtUser.setText("");
this.txtPassword.setText("");
}
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/*
* Set the Nimbus look and feel
*/
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/*
* If Nimbus (introduced in Java SE 6) is not available, stay with the
* default look and feel. For details see
* http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(login.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(login.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(login.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(login.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/*
* Create and display the form
*/
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new login().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JTextField txtPassword;
private javax.swing.JTextField txtUser;
// End of variables declaration
}
回答7:
This works for me.
String.valueOf(txtPass.getPassword())