What's the best way to ask for a password?

I always wondered how to hide stdin, and now I came across a solution: stty -echo. This just stops keystrokes from being printed. Very simple! Naturally, I had to try it in Rust:

use std::{
    error::Error,
    process::Command,
    io::{self, BufRead},
};

fn main() -> Result<(), Box<dyn Error>> {
    println!("\nEnter password:");

    // Hide stdin
    assert_eq!(
        Command::new("stty")
            .arg("-echo")
            .spawn()?
            .wait()?
            .code()
            .unwrap(),

        0
    );

    let mut buf = String::new();
    io::stdin()
        .read_line(&mut buf)
        .unwrap();

    println!("Your password was `{}`", buf.trim());

   // Reset terminal
     assert_eq!(
        Command::new("stty")
            .arg("echo")
            .spawn()?
            .wait()?
            .code()
            .unwrap(),

        0
    );

    Ok(())
}

This works as expected, and prints:

Enter password:
        // User typed `Hello, world`, but it is hidden
Enter password:
Your password was `Hello, world`.

However, I didn't like the fact that the password is typed on a different line, so I tried to make it so that the (hidden) password is typed directly after the Enter password: prompt, like how sudo does it.
Like so:

use std::{
    error::Error,
    process::Command,
    io::{self, BufRead},
};

fn main() -> Result<(), Box<dyn Error>> {
    print!("\nEnter password:");

    // Hide stdin
    assert_eq!(
        Command::new("stty")
            .arg("-echo")
            .spawn()?
            .wait()?
            .code()
            .unwrap(),

        0
    );

    let mut stdin = io::stdin().lock();
    let mut buf = Vec::new();

    stdin.read_until(b"\n"[0], &mut buf)?;

    println!("Your password was `{}`", String::from_utf8(buf)?.trim());

   // Reset terminal
     assert_eq!(
        Command::new("stty")
            .arg("echo")
            .spawn()?
            .wait()?
            .code()
            .unwrap(),

        0
    );

    Ok(())
}

This should read stdin after the prompt, and stopping when a newline is reached.
However, it produces this strange output:

        // No prompt, but stdin is being consumed. User types `Hello, world` and it is hidden

Enter password:Your password was `Hello, world`

I imagine this is a somewhat unavoidable issue caused by stty hiding the whole line or something.

Is there any way to achieve this?

Any help is appreciated. Thanks!

Try calling stdout().flush() after printing the prompt? (stdout flushes automatically after each newline, but in this case you need to flush manually I think)

3 Likes

No, you just need to output \n explicitly. Normally it would come from [an attempt] to specify the password (or anything else) but you have suppressed that thus you need a very explicit \n in the beginning of next line.

Also, calling stty is very strange: it wouldn't work in native Windows programs, anyway, and for POSIX there are termios.

No, that's because stdout is buffered.

As @theemathas wrote, you have to flush after printing your prompt:

std::io::stdout().lock().flush().unwrap();

Normally the flushing occurs automatically, when a newline is printed.

And remember to bring in the proper Write trait:

use std::io::Write;

Beside that, calling an external binary just to fiddle with the console is not common. Maybe you would like to watch out for some crate instead.

1 Like

Take a look at the rpassword crate. Both of you just want a solution for the problem and if you want to learn how to do it.

6 Likes