73

Situation:

I want to get a password entry from the stdin console - without echoing what the user types. Is there something comparable to getpasswd functionality in Go?

What I tried:

I tried using syscall.Read, but it echoes what is typed.

Rene Knop
  • 1,788
  • 3
  • 15
  • 27
RogerV
  • 3,826
  • 4
  • 28
  • 32
  • The ForkExec() call that was used to implement a solution based on invoking 'stty -echo' is now in the syscall package and takes two fewer arguments than previously. – RogerV Jul 17 '11 at 04:15

11 Answers11

133

The following is one of best ways to get it done. First get term package by go get golang.org/x/term

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "syscall"

    "golang.org/x/term"
)

func main() {
    username, password, _ := credentials()
    fmt.Printf("Username: %s, Password: %s\n", username, password)
}

func credentials() (string, string, error) {
    reader := bufio.NewReader(os.Stdin)

    fmt.Print("Enter Username: ")
    username, err := reader.ReadString('\n')
    if err != nil {
        return "", "", err
    }

    fmt.Print("Enter Password: ")
    bytePassword, err := term.ReadPassword(int(syscall.Stdin))
    if err != nil {
        return "", "", err
    }

    password := string(bytePassword)
    return strings.TrimSpace(username), strings.TrimSpace(password), nil
}

http://play.golang.org/p/l-9IP1mrhA

Etienne Bruines
  • 2,848
  • 3
  • 17
  • 25
gihanchanuka
  • 4,783
  • 2
  • 32
  • 32
  • 1
    Does this handle UTF8? – Ztyx Feb 16 '16 at 21:48
  • 9
    you may not want to trim space from password? – Senthil Kumar Feb 16 '18 at 08:53
  • @SenthilKumar I agree with you with referring to https://stackoverflow.com/a/632178/1461060 – gihanchanuka Feb 17 '18 at 18:38
  • This works fine under most circumstances but I needed to workaround this when running in debug mode; the `int(syscall.Stdin)` did not work properly in that context. I followed this in order to get it working: https://stackoverflow.com/questions/47879070/how-can-i-see-if-the-goland-debugger-is-running-in-the-program/47890273#47890273 – cwash Oct 08 '19 at 16:37
  • How can you do this for remote clients connected over TCP? https://stackoverflow.com/questions/61569297/golang-simple-server-with-interactive-prompt – manpatha May 03 '20 at 05:43
29

Just saw a mail in #go-nuts maillist. There is someone who wrote quite a simple go package to be used. You can find it here: https://github.com/howeyc/gopass

It something like that:

package main

import "fmt"
import "github.com/howeyc/gopass"

func main() {
    fmt.Printf("Password: ")
    pass := gopass.GetPasswd()
    // Do something with pass
}
Fatih Arslan
  • 16,499
  • 9
  • 54
  • 55
  • 7
    Note, this package uses [`terminal.MakeRaw`](https://godoc.org/golang.org/x/crypto/ssh/terminal/#MakeRaw) from the Go Author's `golang.org/x/crypto/ssh/terminal` package. That can be used directly if desired. – Dave C Jul 23 '15 at 14:08
  • 11
    Oh, and there is also [`terminal.ReadPassword`](https://godoc.org/golang.org/x/crypto/ssh/terminal/#ReadPassword). – Dave C Jul 23 '15 at 14:14
24

Since Go ~v1.11 there is an official package golang.org/x/term which replaces the deprecated crypto/ssh/terminal. It has, among other things, the function term.ReadPassword.

Example usage:

package main
import (
    "fmt"
    "os"
    "syscall"
    "golang.org/x/term"
)
func main() {
    fmt.Print("Password: ")
    bytepw, err := term.ReadPassword(int(syscall.Stdin))
    if err != nil {
        os.Exit(1)
    }
    pass := string(bytepw)
    fmt.Printf("\nYou've entered: %q\n", pass)
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
11

I had a similar usecase and the following code snippet works well for me. Feel free to try this if you are still stuck here.

import (
    "fmt"
    "golang.org/x/crypto/ssh/terminal"

)

func main() {
    fmt.Printf("Now, please type in the password (mandatory): ")
    password, _ := terminal.ReadPassword(0)

    fmt.Printf("Password is : %s", password)
}

Of course, you need to install terminal package using go get beforehand.

rjni
  • 471
  • 7
  • 7
  • 11
    This crashes for me on windows with `The handle is invalid.`. Had to get actual fd of stdin: `terminal.ReadPassword(int(os.Stdin.Fd()))` – captncraig May 16 '19 at 18:25
7

you can do this by execing stty -echo to turn off echo and then stty echo after reading in the password to turn it back on

jspcal
  • 50,847
  • 7
  • 72
  • 76
  • 1
    Thanks for this clue to use stty (am more familiar with Windows than *nix). Note in the source code solution I provide, I needed to specificaly use Go's ForkExec() call. The ordinary Exec() call replaces the current process with stty process. – RogerV Jan 26 '10 at 08:23
  • 1
    The Go ForkExec() call is now in syscall package and takes two fewer arguments. – RogerV Jul 17 '11 at 04:16
  • 1
    There is better way nowadays - see [my answer](https://stackoverflow.com/a/65852707/485343) below. – rustyx Jan 22 '21 at 21:02
5

Here is a solution that I developed using Go1.6.2 that you might find useful.

It only uses the following standard packages: bufio, fmt, os, strings and syscall. More specifically, it uses syscall.ForkExec() and syscall.Wait4() to invoke stty to disable/enable terminal echo.

I have tested it on Linux and BSD (Mac). It will not work on windows.

// getPassword - Prompt for password. Use stty to disable echoing.
import ( "bufio"; "fmt"; "os"; "strings"; "syscall" )
func getPassword(prompt string) string {
    fmt.Print(prompt)

    // Common settings and variables for both stty calls.
    attrs := syscall.ProcAttr{
        Dir:   "",
        Env:   []string{},
        Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
        Sys:   nil}
    var ws syscall.WaitStatus

    // Disable echoing.
    pid, err := syscall.ForkExec(
        "/bin/stty",
        []string{"stty", "-echo"},
        &attrs)
    if err != nil {
        panic(err)
    }

    // Wait for the stty process to complete.
    _, err = syscall.Wait4(pid, &ws, 0, nil)
    if err != nil {
        panic(err)
    }

    // Echo is disabled, now grab the data.
    reader := bufio.NewReader(os.Stdin)
    text, err := reader.ReadString('\n')
    if err != nil {
        panic(err)
    }

    // Re-enable echo.
    pid, err = syscall.ForkExec(
        "/bin/stty",
        []string{"stty", "echo"},
        &attrs)
    if err != nil {
        panic(err)
    }

    // Wait for the stty process to complete.
    _, err = syscall.Wait4(pid, &ws, 0, nil)
    if err != nil {
        panic(err)
    }

    return strings.TrimSpace(text)
}
minghua
  • 5,981
  • 6
  • 45
  • 71
Joe Linoff
  • 761
  • 9
  • 13
2

Required launching stty via Go ForkExec() function:

package main

import (
    os      "os"
    bufio   "bufio"
    fmt     "fmt"
    str     "strings"
)

func main() {
    fmt.Println();
    if passwd, err := Getpasswd("Enter password: "); err == nil {
        fmt.Printf("\n\nPassword: '%s'\n",passwd)
    }
}

func Getpasswd(prompt string) (passwd string, err os.Error) {
    fmt.Print(prompt);
    const stty_arg0  = "/bin/stty";
    stty_argv_e_off := []string{"stty","-echo"};
    stty_argv_e_on  := []string{"stty","echo"};
    const exec_cwdir = "";
    fd := []*os.File{os.Stdin,os.Stdout,os.Stderr};
    pid, err := os.ForkExec(stty_arg0,stty_argv_e_off,nil,exec_cwdir,fd);
    if err != nil {
        return passwd, os.NewError(fmt.Sprintf("Failed turning off console echo for password entry:\n\t%s",err))
    }
    rd := bufio.NewReader(os.Stdin);
    os.Wait(pid,0);
    line, err := rd.ReadString('\n');
    if err == nil {
        passwd = str.TrimSpace(line)
    } else {
        err = os.NewError(fmt.Sprintf("Failed during password entry: %s",err))
    }
    pid, e := os.ForkExec(stty_arg0,stty_argv_e_on,nil,exec_cwdir,fd);
    if e == nil {
        os.Wait(pid,0)
    } else if err == nil {
        err = os.NewError(fmt.Sprintf("Failed turning on console echo post password entry:\n\t%s",e))
    }
    return passwd, err
}
RogerV
  • 3,826
  • 4
  • 28
  • 32
  • The code does not build with go 1.10.3. Among the errors, the `undefined os.Error` can be fixed by changing `os.Error` to `error`, similar to https://github.com/imbc/go_starter_package/issues/1. There seems to have lots of golang changes. Though thanks for sharing. – minghua Aug 31 '18 at 21:34
2

Here is a version specific to Linux:

func terminalEcho(show bool) {
    // Enable or disable echoing terminal input. This is useful specifically for
    // when users enter passwords.
    // calling terminalEcho(true) turns on echoing (normal mode)
    // calling terminalEcho(false) hides terminal input.
    var termios = &syscall.Termios{}
    var fd = os.Stdout.Fd()

    if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
        syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
        return
    }

    if show {
        termios.Lflag |= syscall.ECHO
    } else {
        termios.Lflag &^= syscall.ECHO
    }

    if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
        uintptr(syscall.TCSETS),
        uintptr(unsafe.Pointer(termios))); err != 0 {
        return
    }
}

So to use it:

fmt.Print("password: ")

terminalEcho(false)
var pw string
fmt.Scanln(&pw)
terminalEcho(true)
fmt.Println("")

It's the TCGETS syscall that is linux specific. There are different syscall values for OSX and Windows.

Colin Fox
  • 53
  • 6
  • 1
    The Go Author's `golang.org/x/crypto/ssh/terminal` package does this exact thing but handles all the OSes supported by Go. (E.g. by using `syscall.TIOCSETA` instead of `syscall.TCSETS` for BSD and using `kernel32.dll` on Windows). – Dave C Jul 23 '15 at 14:12
1

You could also use PasswordPrompt function of https://github.com/peterh/liner package.

hagh
  • 507
  • 5
  • 13
0

Turning off echo before typing and turning on to turn it back on after typing.

Without third library, you can find ways to do with it on unix shown above. But it's difficult on Windows.

You can achieve it by method SetConsoleMode with windows kernel32.dll referring to the accepted answer from C: How to disable echo in windows console?

func GetPassword(prompt string) (err error, text string) {
    var modeOn, modeOff uint32
    stdin := syscall.Handle(os.Stdin.Fd())
    err = syscall.GetConsoleMode(stdin, &modeOn)
    if err != nil {
        return
    }
    modeOff = modeOn &^ 0x0004
    proc := syscall.MustLoadDLL("kernel32").MustFindProc("SetConsoleMode")
    fmt.Print(prompt)
    _, _, _ = proc.Call(uintptr(stdin), uintptr(modeOff))
    _, err = fmt.Scanln(&text)
    if err != nil {
        return
    }
    _, _, _ = proc.Call(uintptr(stdin), uintptr(modeOn))
    fmt.Println()
    return nil, strings.TrimSpace(text)
}
Hwame
  • 1
  • 1
-3

You can get the behavior you want with the Read method from the os.File object (or the os.Stdin variable). The following sample program will read a line of text (terminated with by pressing the return key) but won't echo it until the fmt.Printf call.

package main

import "fmt"
import "os"

func main() {
  var input []byte = make( []byte, 100 );
  os.Stdin.Read( input );
  fmt.Printf( "%s", input );
}

If you want more advanced behavior, you're probably going to have to use the Go C-wrapper utilities and create some wrappers for low-level api calls.

Russell Newquist
  • 2,656
  • 2
  • 16
  • 18
  • This is platform independent, handled by application! Any reason why something like gopass has been written instead of this above snippet? – Sandeep Raju Prabhakar Jan 26 '14 at 09:50
  • 4
    The key difference here is that os.Stdin.Read still echo's the characters to the command prompt which is what the question was trying to avoid. – Owen Allen May 07 '14 at 16:58
  • The assertion that this program won't echo until the Printf() call is incorrect. On Darwin, I see two copies of the inputted string: one when I type it, and one when I hit enter. – Flowchartsman Sep 17 '15 at 21:03
  • Just tried this code on ubuntu amd64 and arm64. It echos when I'm typing, on both platforms. Though thanks for sharing the idea. – minghua Aug 31 '18 at 21:23