Command line reader

While appearing “old-fashioned,” command line programs still exist and are quite useful. In this post, I will share my little command line reader, which provides a few simple readers. It accepts prompt text and handles plaintext and password entry. While the former is quite simple, the latter requires a little background to avoid common mistakes.

Reading from the command line (as read() and read(prompt) do), results in a String return value. While this appears innocuous, it is actually quite unsafe for sensitive information. Why is that? Simply put, Java stores String‘s in a string pool (i.e., Java’s implementation of string interning – storing a single, immutable copy a string and sharing the reference), which is subject to potential attacks. Therefore, sensitive information should only be read via char[] which can be (1) overwritten and (2) destroyed by the Garbage Collector (eventually). The next obvious question is, do we mask characters (i.e., replace with a bullet point) or display nothing at all (e.g., Linux terminal password prompts)? As the former simply replaces one character with another, it might be possible to alter the replacement font, resulting in the actual password being displayed. Even without going to those lengths, character replacement still displays the length of the password to all in viewing distance. Thus, I recommend a zero-output display like the one implemented in password(prompt), which is System.console().readPassword().

import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import static java.lang.System.err;
import static java.lang.System.out;
 
/**
 * Command line reader that handles plaintext and password (zero-display).
 * @author Ray Hylock
 */
public class CLIReader {
    private static final String PROMPT = ">";
     
    /** Do not instantiate. */
    private CLIReader(){}
     
    /**
     * Reads the input from the command line and passes the results back.
     * @return  the input String
     */
    public static String read() {
        return read(null);
    }
     
    /**
     * Reads the input from the command line and passes the results back. The 
     * prompt is altered from base {@literal >} output the include the passed 
     * prompt text at the head of the {@literal >}. E.g., text {@literal >}.
     * @param prompt    the prompt text
     * @return          the input String
     */
    public static String read(String prompt) {
        out.print(getPrompt(prompt));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = null;
        try {
            input = br.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read the input!");
            ioe.printStackTrace();
            System.exit(1);
        }
        return input;
    }
     
    /**
     * Reads a password from the command line and passes the result back. This 
     * uses {@link Console#readPassword()} which does not print the
     * characters to the screen.
     * @param prompt    the prompt text
     * @return          the password as a {@code char[]}
     */
    public static char[] password(String prompt){
        Console c = System.console();
        if(c == null){
            err.println("Error, cannot create console.");
            System.exit(1);
        }
        char[] p = c.readPassword(getPrompt(prompt));
        return p;
    }
     
    /**
     * Return the command line prompt text.
     * @param prompt preface prompt text
     * @return       prompt to output
     */
    private static String getPrompt(String prompt){
        return ((prompt == null || prompt.length() == 0) ? "" : prompt + " ") 
                + PROMPT + " ";
    }
}

An example implementation is as follows:

import static java.lang.System.out;
import java.util.Arrays;
public class CLIReaderTest {
    public static void main(String[] args) {
        // enter username
        String username = CLIReader.read("username");
        out.println("Username: " + username);
         
        // enter password, display, and cleanup
        char[] password = CLIReader.password("password");
        out.println("Password: " + new String(password));
        Arrays.fill(password, 'z'); // set all values to 'z' - random char
        password = null;            // dissallow access and setup for GC
    }
} 

The output for this example given a username of “user” and password of “pass” is:

username > user
Username: user
password > 
Password: pass