26

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.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • I question the claim that you'd need to rewrite the Swing API to display sensitive data. Can you make a custom component for this, which extends JComponent and overrides paintComponent? Then it is up to you as to how the text is processed. – Steve McLeod Jun 12 '09 at 07:15

7 Answers7

48

This works for me and helps you to build a Stringified password:

String passText = new String(passField.getPassword());
Ali Ben Messaoud
  • 11,690
  • 8
  • 54
  • 87
  • 15
    And thus you are thoroughly defeating the entire purpose of having the password in a char array. – A.Grandt Oct 02 '14 at 15:13
  • 2
    @A.Grandt while your comment is technically very correct, it also is utterly useless. Password validations is generally done against a database and almost always sent as a string. For instance, you might instead explain how to use a char array in a JPA createQuery setParameter(string name, string value) context, without making use of a string, if at all possible. – Tuncay Göncüoğlu Oct 13 '15 at 13:22
  • 3
    @Tuncay I sincerely hope you aren't suggesting that anyone in their right mind stores passwords in clear text in their databases? Doing so is insane beyond comprehension. Passwords are to be thoroughly hashed with a secure, as in NOT a home brewed code, using at the very least PBKDF2WithHmacSHA1 or PBKDF2WithHmacSHA256, with a large number of iterations, and a salt value randomly generated individually for each password. All of this happens as a byte or char array, as you can securely clear their content after use. Only the hashed password can, or should be presented in a String. – A.Grandt Oct 14 '15 at 13:41
  • 1
    @A.Grandt This line is used to get password after the submission of the form by the user, so, it must be encrypted before persisting ti in the database. So I totally agree with you. – Ali Ben Messaoud Oct 14 '15 at 14:18
  • @albienmessaoud My slightly scorching comment above was aimed at Tuncay's comment. However the way I usually do it is to hash the new password with PBKDF2WithHmacSHA256, attach the iteration count and salt values using for instance : as a separator, remembering to clear the arrays afterwards. Validation does add one more DB call though, as you first get the password stored for the user given in the login, get the salt and iterations used, rehash the new PW using those values and compare the result. that way you can increase iterations and key/salt size later without breaking existing PW's – A.Grandt Oct 14 '15 at 15:42
  • 2
    @A.Grant of course I'm not. I'm merely pointing out that at one point along the line you are almost certain to need a string. to sha256 it for instance, or to encrypt it, or base64 it... – Tuncay Göncüoğlu Oct 15 '15 at 12:02
  • @Tuncay Göncüoğlu Not really. Most encryption functions only takes byte or char arrays for the sensitive data, including properly implemented Base64 functions. Yes, if you are sending the data over the internet you are essentially going to have the password given as a text, though in that case you are naturally using an SSL connection. – A.Grandt Nov 19 '15 at 11:06
  • -1 You don't need to convert the password to a string. Ever. Most crypto providers take a byte array as input. This answer, like it's already been pointed out, defeats the purpose of having a char array. – NullUserException Mar 23 '16 at 18:30
  • ... almost half the rent, creating a String. At the end sensitive data might remain in the heap memory of the JRE. For not getting compromised, you will need to collect each representation (String) of the password an destroy it, meaning overwriting the char array buffer of the String. – Sam Ginrich Aug 07 '21 at 17:46
25

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.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
  • 1
    I'm surprised; it took me about 15 minutes to spelunk through the Swing code and assure myself that nothing tricky was happening. – Michael Myers Jun 11 '09 at 22:22
  • According to my observation the Segment gets the original array: When you overwrite the Segment's array field (with zeros, using reflection), next getText(...)-call returns an empty array. So this would be a way to destroy the readable version of the password in the JPasswordField. – Sam Ginrich Aug 07 '21 at 16:38
5

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

  • 1
    You're right. I thought I was seeing the Segment's buffer get zeroed, but it isn't. Perhaps you should file a bug with Sun. – Michael Myers Jun 12 '09 at 12:28
  • Still the same 6 years later in Java 8. Segment is not cleared and is just left for the GC to handle. – A.Grandt Oct 14 '15 at 16:13
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.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • "A vaguely modern VM will move objects in memory anyway, so clearing the char[] does not necessarily work." That's a very good point. +1 for that alone, before reading the rest. – Michael Myers Jun 11 '09 at 23:43
  • Why would a moving GC make clearing char[] ineffective? Are you saying that it's possible in the instant after the char[] is written with the password but before it is cleared, the GC runs and copies the array to somewhere else, leaving the original copy of the password as "garbage" to be overwritten by later allocations? – Jason C Mar 26 '10 at 16:41
  • 1
    @Jason Yes. Although I believe typical implementations block zero memory (at least in "Eden"-like spaces) rather than do it at allocation time. It's also more likely that code cockups will cause actual problems - the action source, did you remember to clear the document, getText may create an extra `char[]` (but you don't know so you can't clear it), document implementation may reallocate buffer, etc., etc. – Tom Hawtin - tackline Mar 26 '10 at 17:39
  • "A vaguely modern VM will move objects in memory anyway, so clearing the char[] does not necessarily work." A little late to the party, but here goes. If a VM moves the address space around, it must be the one to clear up the space it came from. But if it did how can JNI even work then? In JNI you get access to the physical address space, and arrays are allocated in that space as well. I've been there and done that. It was not as much fun as I'd have hoped. – A.Grandt Oct 14 '15 at 15:50
  • 1
    @A.Grandt You're supposed to "pin" objects before you access them directly in JNI. I understand it's not a nice API and does interfere with GC. – Tom Hawtin - tackline Oct 14 '15 at 15:54
2

**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 Strings, no cut/copy).

Draemon
  • 33,955
  • 16
  • 77
  • 104
0
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                   
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
Jayson
  • 1
  • 1
    thanks for trying to be helpful :-) Unfortunately, your code doesn't address the base issue raised by the OP (which was whether or not a call to getPassword opens a security hole). BTW: please learn java naming conventions and stick to them – kleopatra Apr 03 '12 at 09:40
-2

This works for me.

String.valueOf(txtPass.getPassword())
Ashish Kakkad
  • 23,586
  • 12
  • 103
  • 136
  • The key to dealing with passwords is to keep them in a char/byte array, the moment you convert it to a string you lose control of the address space it occupies, as Java strings are immutable (you can't change their content, every change you make to a String creates a new copy in memory). You can clear an array by iterating over it, or use Arrays.fill(array, value) and it'll overwrite the actual data in the memory, making it irretrievable after its use. – A.Grandt Oct 14 '15 at 16:00