What is most secure and simplest way to have a user-typed password on bash become part of stdin to a program?

With bash or zsh:

unset -v password # make sure it's not exported
set +o allexport  # make sure variables are not automatically exported
IFS= read -rs password < /dev/tty &&
  printf '{"username":"myname","password":"%s"}\n' "$password" | cmd

Without IFS=, read would strip leading and trailing blanks from the password you type.

Without -r, it would process backslashes as a quoting character.

You want to make sure you only ever read from the terminal.

echo can't be used reliably. In bash and zsh, printf is builtin so that command line wouldn't show in the output of ps.

In bash, you need to quote $password as otherwise the split+glob operator is applied to it.

That's still wrong though as you'd need to encode that string as JSON. For instance, double-quote and backslash at least would be a problem. You probably need to worry about the encoding of those characters. Does your program expect UTF-8 strings? What does your terminal send?

To add a prompt string, with zsh:

IFS= read -rs 'password?Please enter a password: '

With bash:

IFS= read -rsp 'Please enter a password: ' password

Here's the solution I have (it's been tested):

$ read -s PASSWORD
<user types password>
$ echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"'

Explanation:

  1. read -s reads a line of stdin (the password) without echoing the line to the screen. It stores is in the shell variable PASSWORD.
  2. echo -n $PASSWORD puts the password on stdout without any newline. (echo is a shell built-in command so no new process is created so (AFAIK) the password as an argument to echo will not be shown on ps.)
  3. perl is invoked and reads the password from stdin to $_
  4. perl puts the password in $_ into the full text and prints it to stdout

If this is going to a HTTPS POST, the second line would be something like:

echo -n $PASSWORD | perl -e '$_=<>; print "{\"username\":\"myname\",\"password\":\"$_\"}"' | curl -H "Content-Type: application/json" -d@- -X POST <url-to-post-to>

If your version of read doesn't support -s you try the POSIX compatible way seen in this answer.

echo -n "USERNAME: "; read uname
echo -n "PASSWORD: "; set +a; stty -echo; read passwd; command <<<"$passwd"; set -a; stty echo; echo
passwd= # get rid of passwd possibly only necessary if running outside of a script

The set +a is supposed to prevent auto "export" of a variable to the environment. You should check out the man pages for stty, there are lots of options available. The <<<"$passwd" is quoted because good passwords can have spaces. The last echo after enabling the stty echo is to have the next command/output start on a new line.