1

Is there a simple way to fill the document of a JPasswordField without creating a String object that contains the password?

I was trying to create a "change password" dialog which takes in the old password and requires the new one to be entered twice (three password fields), where the old password may be known before hand, depending on how the user configured this (password may have been stored). So instead of requiring the user to enter the existing password each time the associated dialog is shown to her, I wanted to fill it out programmatically.

Note that JPasswordField.setText(String) and the String constructor are not an option. I'd like to do this using a char array.

I've been trying to abuse GapContent which seems to be used by PlainDocument, but it doesn't seem to work (the chars are in there but the field is corrupted):

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JFrame;
import javax.swing.JPasswordField;
import javax.swing.text.PlainDocument;
import javax.swing.SwingUtilities;
import javax.swing.text.GapContent;

public class FillJPasswordField extends JFrame {

    private JPasswordField pass;

    public FillJPasswordField() {
        setLayout(new GridBagLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
        PlainDocument doc = new PlainDocument(new MyGapContent(password));

        pass = new JPasswordField(doc, null, 20);

        // see if the password is in there
        System.out.println(new String(pass.getPassword()));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0d;
        gbc.insets = new Insets(10, 5, 10, 5);

        add(pass, gbc);

        pack();
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new FillJPasswordField().setVisible(true);
            }
        });
    }

    private class MyGapContent extends GapContent {

        public MyGapContent() {
            super();
        }

        public MyGapContent(int initialLength) {
            super(initialLength);
        }

        public MyGapContent(char[] content) {
            this(content.length);
            replace(0, 0, content, content.length);
        }

    }
}
predi
  • 5,528
  • 32
  • 60
  • 1
    Can't you fill the `old-password` field with as many asterisks (*) as the old password has ? You can store the amount in an int, without storing the actual password. Also, if you're storing a password make sure you hash it first. (For help on hashing: http://stackoverflow.com/questions/2860943/suggestions-for-library-to-hash-passwords-in-java) – Charlie Nov 17 '14 at 14:57
  • @Charlie, I can fake it, but would like to know if filling it with the actual value is possible. – predi Nov 17 '14 at 15:02
  • I don't fully understand your question, but can't you make a `String` from a `char[]` using `new String(char[])` and use that? – Charlie Nov 17 '14 at 15:08
  • @Charlie, as stated in the question, I'd like to avoid doing that. For the same reasons as `JPasswordField` implementation itself. – predi Nov 17 '14 at 15:10
  • 1
    Even if technically possible, e.g. via reflection, you'd lose the security benefit of always requiring the old password; handle forgotten a password separately. – trashgod Nov 17 '14 at 15:12
  • @trashgod, this is not a case of "forgotten password". My app can store credentials on behalf of the user using two way encryption. – predi Nov 17 '14 at 15:17
  • I'd still ask as a way to reject a hijacked session. – trashgod Nov 17 '14 at 21:18

1 Answers1

0

Hmm.. The following seems to work:

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JFrame;
import javax.swing.JPasswordField;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.GapContent;
import javax.swing.text.PlainDocument;

public class FillJPasswordField extends JFrame {

    private JPasswordField pass;

    public FillJPasswordField() {
        setLayout(new GridBagLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};

        MyGapContent content = new MyGapContent();
        PlainDocument doc = new PlainDocument(content);
        try {
            content.insertChars(0, password);
        } catch (BadLocationException ex) {
        }
        pass = new JPasswordField(20);
        pass.setDocument(doc);

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.PAGE_START;
        gbc.weightx = 1.0d;
        gbc.insets = new Insets(10, 5, 10, 5);
        add(pass, gbc);

        System.out.println(new String(pass.getPassword()));

        pack();
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new FillJPasswordField().setVisible(true);
            }
        });
    }

    private class MyGapContent extends GapContent {

        public MyGapContent() {
            super();
        }

        public MyGapContent(int initialLength) {
            super(initialLength);
        }

        public void insertChars(int where, char[] chars) throws BadLocationException {
            if (where > length() || where < 0) {
                throw new BadLocationException("Invalid insert", length());
            }
            replace(where, 0, chars, chars.length);
        }
    }
}

Previous code didn't seem to like the fact that I was calling replace inside the constructor of MyGapContent. Perhaps it requires operations to be done in some sequence.

predi
  • 5,528
  • 32
  • 60