为什么JPasswordField.getPassword()用它的密码创build一个String?

Swing的JPasswordField具有返回char数组的getPassword()方法。 我的理解是这个数组可以在使用后立即清零,这样你就不会有敏感的东西在内存中长时间的存在。 旧的方法来检索密码是使用getText() ,它返回一个string对象,但它已被弃用。

所以,我的问题是,为什么它在使用getPassword()的检索过程中实际上被Java使用? 更清楚的是,我正在debugging我的testing应用程序的其他事情**,我跟着调用和爆炸… JPasswordField getText()被调用,当然,我的密码已经创build了一个不错的String对象,现在是挂在记忆中。

自己尝试一下:

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

后续问题:是否以任何方式“隐藏”使用getText()危险? 当然,一个专门的攻击者会得到你的密码,如果它已经损害了系统,我说的是一个不那么专注的;)

**当我正在寻找一种方法在Swing组件上实际显示一些敏感数据而不使用String对象时,我遇到了这个问题。 显然有没有办法做到这一点,除非我愿意重写Swing API的部分(全部)..不会发生。

这适用于我:

码:

 String passText = new String(passField.getPassword()); 

实际上,下面是getPassword()的Sun实现:

 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; } 

这里唯一的getTextgetText(int offset, int length, Segment txt)调用,它调用getChars(int where, int len, Segment txt) ,然后将字符直接复制到Segment的缓冲区中。 这里没有创buildStrings

然后, Segment的缓冲区被复制到返回值中并在方法返回之前清零。

换句话说: 在任何地方都没有额外的密码副本 。 只要按照指示使用它,它就是完全安全的。

好吧,我的坏…所有的钟声开始响起,只要我看到调用getText(),而没有注意到它实际上是由我与行动侦听器引入这里是一个堆栈跟踪

 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 

这里是使用的代码:

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

这里是控制台输出:

 Awhooa: secret secret 

而对于getPassword()的实际调用,也许我失去了一些东西,但在哪里是段的缓冲区归零? 我看到一个数组副本,但不是一个调零。 返回的数组将被我自己归零,但Segment的数组仍然存在…

 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)); } } 

而输出:

 Before zeroing: [a, b, c] [a, b, c] After zeroing: [?, ?, ?] [a, b, c] 

(我在那里放了问号,因为我不能通过不可打印的字符)

-M

Swing的实现过于复杂,无法手工检查。 你想要testing。

 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); }}; } }); } } 

看起来像(无意义的)行动事件给我的命令string。 还有其他方法可以产生效果。

一个模糊的现代虚拟机将移动内存中的对象,所以清除char[]不一定工作。

**当我正在寻找一种方法在Swing组件上实际显示一些敏感数据而不使用String对象时,我遇到了这个问题。 显然有没有办法做到这一点,除非我愿意重写Swing API的部分(全部)..不会发生。

您可以通过调用field.setEchoChar('\0')来告诉JPasswordField显示字符。 这保留了JPasswordField提供的其他保护(没有String ,没有剪切/复制)。

 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 } 

这对我有用。

 String.valueOf(txtPass.getPassword())