Articles Index
One of the ongoing criticisms of the Java command line text-based
input/output APIs is the lack of support for command line input
password masking. With AWT/Swing, this is not a problem as methods are
provided to mask passwords easily.
An earlier version of this article was published here in September
2002, and since then numerous letters of thanks, constructive comments,
and permissions to use the source code in applications have been
received. This article:
- Presents a brief overview of password masking
- Describes the AWT/Swing utilities for password masking
- Provides a platform-independent solution to the command-line input password masking problem
- Provides an improved (reliable and secure) solution to password masking
Password Masking
Login screens and login dialog boxes use password masking — a
technique to either hide the password as it is being typed or
displaying a character (such as an asterisk '*') instead of the
characters that users type. For example, when you login on a Windows
machine, you are presented with a login dialog box in which the
password field uses an asterisk as the masking or echo character.
 |
Figure 1: UNIX login screen
|
|
In the case of UNIX, the password field in the login screen doesn't
display echo characters. It simply doesn't display anything at all as
shown in Figure 1.
Password Masking in AWT/Swing
If you wish to provide a graphical login dialog box for your application, you can use the AWT's TextField class, which is a text component that allows the editing of a single line of text. To mask the password field, use the setEchoChar method. For example, to set the echo char to an asterisk, you would do:
TextField password = new TextField(8);
password.setEchoChar('*');
|
The number 8 specifies the width of the text field based on the
average character width for the font in use. You can set the echo
character to any character you like. Note that if you set it to zero, 0 , it means that the input will be echoed and not masked.
 |
Figure 2: JPasswordField with setEchoChar('#')
|
|
In Swing, you can use the JPasswordField , which
allows the editing of a single line of text where the view indicates
something was typed, but does not show the original characters. The JPasswordField class is source-compatible with the AWT's TextField used with setEchoChar . If you use the JPasswordField ,
the default echo character is an asterisk '*', but you can change it to
any character of your choice. Again, if you set the echo character to
zero, 0 , it means that characters will be displayed as
they are typed and no masking will be performed. Figure 2 shows a Swing
login dialog box where the echo character is being set to a hash sign, # using the following snippet of code:
JPasswordField password = new JPasswordField(8);
password.setEchoChar('#');
|
Command-Line Input Masking
Unlike AWT/Swing, there are no special APIs for masking command-line
input in Java. This is a feature that has been asked for by many
developers. It is useful if you wish to provide a login screen for
command-line text-based Java applications as well as server-side Java
applications. One way to provide for such a feature is to use Java
Native Interface (JNI). This might be difficult for some Java
developers who do not know C/C++, or wish to keep to 100% pure Java
code.
Here I provide a solution to this problem. In the earlier version of
this article, a UNIX-like approach to login screens, where the password
is not echoed on the screen at all, was used. This is done by having a
separate thread that attempts to erase characters echoed to the console
by re-writing and printing the password prompt. The code featured in
that article can still be downloaded from the forums along with code for improvements.
One of the most asked-for features, however, was replacing the echoed
characters with asterisks "*". Therefore, this article starts by
providing a simple solution for password masking, followed by an
improved, more reliable, and secure code.
Simple Solution
This solution uses a separate thread to erase the echoed characters
as they are being entered, and replaces them with asterisks. This is
done using the EraserThread class, which is shown in Code Sample 1.
Code Sample 1: EraserThread.java
import java.io.*;
class EraserThread implements Runnable {
private boolean stop;
/**
*@param The prompt displayed to the user
*/
public EraserThread(String prompt) {
System.out.print(prompt);
}
/**
* Begin masking...display asterisks (*)
*/
public void run () {
stop = true;
while (stop) {
System.out.print("\010*");
try {
Thread.currentThread().sleep(1);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
/**
* Instruct the thread to stop masking
*/
public void stopMasking() {
this.stop = false;
}
}
|
Note: The solution makes extensive use of threads, however, if the machine is under heavy load, there is no guarantee that the MaskingThread will run often enough. Please see the rest of the article for an improved version of the code.
The EraserThread class is used by the PasswordField class, which is shown in Code Sample 2. This class prompts the user for a password and an instance of EraserThread attempts to mask the input with "*". Note that initially a asterisk (*) will be displayed.
Code Sample 2: PasswordField.java
public class PasswordField {
/**
*@param prompt The prompt to display to the user
*@return The password as entered by the user
*/
public static String readPassword (String prompt) {
EraserThread et = new EraserThread(prompt);
Thread mask = new Thread(et);
mask.start();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String password = "";
try {
password = in.readLine();
} catch (IOException ioe) {
ioe.printStackTrace();
}
// stop masking
et.stopMasking();
// return the password entered by the user
return password;
}
}
|
As an example of how to use the PasswordField class, consider the application, TestApp ,
shown in Sample Code 3. This application displays a prompt and waits
for the user to enter a password. The entry is of course masked by
asterisks (*).
Code Sample 3: TestApp.java
class TestApp {
public static void main(String argv[]) {
String password = PasswordField.readPassword("Enter password: ");
System.out.println("The password entered is: "+password);
}
}
|
 |
Figure 3: TestApp Sample Output
|
|
If you run TesApp on Windows, MacOS, or UNIX, you
will see something similar to Figure 3. Again, note that when you run
the application, an initial asterisk is displayed.
Making the Code Secure and Reliable
The above simple solution suffers from one main drawback: strings should not be used for storing sensitive information such as passwords!. In the rest of the article, an improved solution is shown.
First, however, the MaskingThread class could benefit from a couple of improvements:
- In order to ensure visibility across threads, especially on multi-CPU machines, the
stop field should be marked volatile . The volatile
keywords specifies that the field is used by synchronized threads so
that the compiler should not perform any optimizations with it; in
other words, the variable's value should be read from memory and no
copy should be saved on the stack.
- To ensure that masking can occur when the system is under heavy
use, the calling thread priority is set to the max for the duration of
the call. The original priority is restored upon return.
Code Sample 4 shows the revised MaskingThread class with the changes highlighted in bold text.
Code Sample 4: MaskingThread.java
import java.io.*;
/**
* This class attempts to erase characters echoed to the console.
*/
class MaskingThread extends Thread {
private volatile boolean stop;
private char echochar = '*';
/**
*@param prompt The prompt displayed to the user
*/
public MaskingThread(String prompt) {
System.out.print(prompt);
}
/**
* Begin masking until asked to stop.
*/
public void run() {
int priority = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
stop = true;
while(stop) {
System.out.print("\010" + echochar);
try {
// attempt masking at this rate
Thread.currentThread().sleep(1);
}catch (InterruptedException iex) {
Thread.currentThread().interrupt();
return;
}
}
} finally { // restore the original priority
Thread.currentThread().setPriority(priority);
}
}
/**
* Instruct the thread to stop masking.
*/
public void stopMasking() {
this.stop = false;
}
}
|
While it may seem logical to collect and store the password using String s, they are not suitable for storing sensitive information such as passwords. This is because objects of type String are immutable -- the contents of the string cannot be changed or overwritten after use. An array of char s should be used instead. The revised PasswordField shown in Code Sample 5 has been adapted from Using Password-Based Encryption.
Code Sample 5: PasswordField.java
import java.io.*;
import java.util.*;
/**
* This class prompts the user for a password and attempts to mask input with "*"
*/
public class PasswordField {
/**
*@param input stream to be used (e.g. System.in)
*@param prompt The prompt to display to the user.
*@return The password as entered by the user.
*/
public static final char[] getPassword(InputStream in, String prompt) throws IOException {
MaskingThread maskingthread = new MaskingThread(prompt);
Thread thread = new Thread(maskingthread);
thread.start();
char[] lineBuffer;
char[] buf;
int i;
buf = lineBuffer = new char[128];
int room = buf.length;
int offset = 0;
int c;
loop: while (true) {
switch (c = in.read()) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) {
if (!(in instanceof PushbackInputStream)) {
in = new PushbackInputStream(in);
}
((PushbackInputStream)in).unread(c2);
} else {
break loop;
}
default:
if (--room < 0) {
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
Arrays.fill(lineBuffer, ' ');
lineBuffer = buf;
}
buf[offset++] = (char) c;
break;
}
}
maskingthread.stopMasking();
if (offset == 0) {
return null;
}
char[] ret = new char[offset];
System.arraycopy(buf, 0, ret, 0, offset);
Arrays.fill(buf, ' ');
return ret;
}
}
|
Finally, the PasswordApp class shown in Code Sample 6 is just a test application to test the revised code.
Code Sample 6: PasswordApp.java
import java.io.*;
public class PasswordApp {
public static void main(String argv[]) {
char password[] = null;
try {
password = PasswordField.getPassword(System.in, "Enter your password: ");
} catch(IOException ioe) {
ioe.printStackTrace();
}
if(password == null ) {
System.out.println("No password entered");
} else {
System.out.println("The password entered is: "+String.valueOf(password));
}
}
}
|
 |
Figure 4: PasswordApp Sample Output
|
|
If you run PasswordApp on Windows, MacOS, or UNIX, you will see something similar to Figure 4.
Conclusion
This article presented an overview of password masking in Java. It
demonstrated how easy it is to mask passwords in AWT/Swing
applications, and provided a reusable pure Java solution for
command-line text-based password masking.
Feel free to reuse, improve, and adapt the code in this article for
your applications. You can enhance it by adding methods for password
constraints. As an exercise, you may wish to enhance the code presented
so that a password can be of a certain length, and so that some
characters, such as a space for example, may not be allowed in a
password.
For More Information
Acknowledgment
Special thanks to Alan Sommerer, a Staff Engineer at Sun Microsystems, for his contributions to this article.
|