Skip to Content
Course content

205: Reading Passwords Securely

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Why can't I just use std::io::stdin().read_line()?

You can, but you probably shouldn't. The problem isn't with Rust's safety guarantees, but with how terminals work. By default, terminals operate in "echo" mode. When you type a character, the terminal sends it to your program and immediately prints it back to the screen so the user can see what they're typing.

If you're building a CLI tool where a user enters a password, you don't want their secret splashed across the screen for anyone glancing over their shoulder. To fix this, you have to tell the terminal to disable echoing, read the input, and then—this is the important part—turn echoing back on so the user's terminal isn't broken for the next command they run.

How do I actually stop the terminal from echoing my typing?

Handling terminal modes manually is a nightmare because it's platform-specific. Linux and macOS use termios, while Windows does things completely differently. I wouldn't recommend writing your own wrapper for this unless you really enjoy reading C headers from the 80s.

Instead, use the rpassword crate. It's the industry standard for this in Rust. It handles the cross-platform gymnastics for you. Here is how you'd implement a simple password prompt:

[dependencies]
rpassword = "7.3"

fn main() {
    println!("Enter your secret key:");
    // read_password() handles disabling the echo and cleaning up afterward
    let password = rpassword::read_password().expect("Failed to read password");
    
    println!("Password received! Length: {}", password.len());
}

It's straightforward. The read_password() function blocks the thread, hides the input, and returns a String. Simple, right? Well, almost.

Once I have the password in a String, is it actually secure?

Here is where things get tricky. You now have a String on the heap. In Rust, when a String goes out of scope, the memory is freed, but it isn't necessarily zeroed. The bytes of that password might just sit in your RAM until that specific block of memory is overwritten by something else.

If an attacker manages to get a memory dump of your process, they might find that password lying around in "dead" memory. For most apps, this is an acceptable risk. But if you're building something like a password manager or a crypto wallet, it's not enough. You want to "zeroize" the memory as soon as you're done with it.

I usually suggest the secrecy crate for this. It wraps your sensitive data in a SecretString, which prevents the value from being accidentally printed to logs (via Debug) and helps ensure the memory is handled more carefully. If you're really paranoid, you can use the zeroize crate to manually overwrite the memory with zeros before the variable is dropped.




📋 Practical Task

Build a Secure CLI Vault Login

Create a small command-line program that simulates a secure login for a vault. Your program must meet the following requirements:

  • Prompt the user for a username using standard std::io::stdin() (this should be visible).
  • Prompt the user for a password using the rpassword crate (this must be hidden).
  • Compare the input against a hardcoded set of credentials (e.g., Username: "admin", Password: "rust_is_safe").
  • If the credentials match, print "Access Granted"; otherwise, print "Access Denied".
  • Ensure that the password variable is dropped as soon as the comparison is finished.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.