Golang read password from stdin: x/term ReadPassword and TTY checks

Golang read password from stdin with golang.org/x/term ReadPassword: pkg.go.dev ReadPassword IsTerminal, example os.Stdin.Fd and syscall.Stdin on Linux, ReadPassword Windows and platforms, no echo vs golang asterisk masking, and bcrypt hashing after read.

Published

Updated

Read time 3 min read

Reviewed byDeepak Prasad

Golang read password from stdin: x/term ReadPassword and TTY checks

Searches like golang read password from stdin, golang.org/x/term readpassword example, pkg.go.dev golang.org/x/term readpassword isterminal, and golang.org/x/term readpassword windows usually point to the same API: term.ReadPassword from golang.org/x/term. The upstream doc string states it reads a line of input from a terminal without local echo and that the returned slice does not include \n. Queries such as pkg.go.dev golang.org/x/term readpassword and golang.org/x/term readpassword platforms are really about that contract: you must pass a terminal file descriptor, not only pick Windows versus Linux.

What ranking tutorials and issues usually add beyond a one-line example: guard with term.IsTerminal, print your own newline after ReadPassword (Enter does not produce a visible line break), and branch when stdin is a pipe—see for example Cesar Gimenes on capturing terminal passwords in Go and ataraskov.dev on pipes and passwords; the historical discussion in golang/go#19909 explains why redirected stdin breaks naive ReadPassword use.

Tested with Go 1.24 on Linux (build and local TTY run; ReadPassword needs an interactive terminal).


Install golang.org/x/term

From a module:

bash
go get golang.org/x/term@latest

Minimal ReadPassword example (os.Stdin.Fd)

The signature is func ReadPassword(fd int) ([]byte, error). Passing int(os.Stdin.Fd()) matches the package examples and avoids assuming descriptor 0 on every platform (the term package header calls this out for non-Unix systems). On Linux, syscall.Stdin is typically 0 and matches stdin as well—queries like golang.org/x/term readpassword example syscall.stdin are about that constant—but os.Stdin remains the portable handle.

go
package main

import (
	"fmt"
	"os"

	"golang.org/x/term"
)

func main() {
	fd := int(os.Stdin.Fd())
	if !term.IsTerminal(fd) {
		fmt.Fprintln(os.Stderr, "stdin is not a terminal; cannot use ReadPassword here")
		os.Exit(1)
	}

	fmt.Print("Password: ")
	pw, err := term.ReadPassword(fd)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println() // ReadPassword does not emit a newline after Enter

	fmt.Printf("read %d bytes (secret not printed)\n", len(pw))
}

Run with go run . in a real terminal (not an IDE “Run” pane without a TTY). You type blind; after Enter, the program prints a blank line then the length line.


IsTerminal, pipes, and platforms

pkg.go.dev golang.org/x/term readpassword isterminal and golang.org/x/term readpassword isterminal platforms boil down to: call term.IsTerminal(fd) on the same fd you pass to ReadPassword. If it is false, fall back—for example read a line with bufio when a script pipes a secret—so CI and non-interactive runners do not see inappropriate ioctl for device.


Optional: hash with bcrypt after reading

Hashing is separate from hiding input. After you have []byte from ReadPassword, you can hash with bcrypt.GenerateFromPassword. Prefer bcrypt.DefaultCost (or a documented cost) rather than an extreme value that slows legitimate logins.

go
import "golang.org/x/crypto/bcrypt"

hash, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost)

Compare later with bcrypt.CompareHashAndPassword.


Golang asterisk masking (not ReadPassword)

ReadPassword implements no-echo reading, not per-character * display (golang asterisk). Masked echo usually means raw terminal mode plus manual drawing, or a TUI library. For most CLIs, silent ReadPassword is the idiomatic choice.


Summary

golang read password from stdin in production Go code is usually term.ReadPassword(int(os.Stdin.Fd())) together with term.IsTerminal, an explicit newline, and a non-TTY fallback—patterns repeated across official comments, pkg.go.dev golang.org/x/term readpassword documentation, and high-ranking community posts. golang.org/x/term readpassword windows and Unix both go through the same API; golang.org/x/term readpassword example os.stdin.fd is the portable FD choice, while syscall.Stdin is mostly a Linux shortcut. Separate bcrypt (or another KDF) from the capture step, and treat golang asterisk masking as a different feature than ReadPassword.


References


Frequently Asked Questions

1. What does pkg.go.dev say golang.org/x/term ReadPassword does?

It reads a line from a terminal without local echo; the returned byte slice does not include the newline character (see the function comment in the upstream term package).

2. Should I use syscall.Stdin or os.Stdin.Fd() with ReadPassword?

On Linux, syscall.Stdin is often 0 and matches stdin’s descriptor, but the term package documentation notes that on some systems stdin’s FD is not necessarily 0; prefer int(os.Stdin.Fd()) for the value you pass to ReadPassword and IsTerminal.

3. Why check term.IsTerminal before ReadPassword?

ReadPassword expects a terminal FD; when stdin is a pipe or file, calls can fail with errors such as inappropriate ioctl for device—matching what many production CLIs and community writeups recommend (see the body for links).

4. Does golang.org/x/term ReadPassword work on Windows?

The golang.org/x/term module implements terminal helpers for Windows as well as Unix; still use int(os.Stdin.Fd()) and handle non-interactive stdin the same way you would on Linux.

5. Is golang asterisk masking the same as ReadPassword?

ReadPassword turns echo off entirely; showing * per keystroke is a different UI pattern, usually from a TUI library or custom raw-mode reader, not from ReadPassword alone.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …