Go语言中的getpasswd功能是什么?

73

情境:

我想从stdin控制台中获取密码输入 - 而不显示用户所输入的内容。在Go语言中是否有类似于getpasswd的功能?

我尝试了:

我尝试使用syscall.Read,但它会回显输入的内容。


ForkExec()调用被用于实现基于调用'stty -echo'的解决方案,现在已经在syscall包中,并且比以前少了两个参数。 - RogerV
11个回答

133

以下是完成此任务的最佳方法之一。 首先通过go get golang.org/x/term获取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


1
这个处理UTF8吗? - Ztyx
11
你可能不想从密码中删除空格? - Senthil Kumar
@SenthilKumar 我同意你提到的 https://dev59.com/e3RB5IYBdhLWcg3wa2m2#632178。 - gihanchanuka
这在大多数情况下都可以正常工作,但在调试模式下需要解决问题;在那种情况下,int(syscall.Stdin) 无法正常工作。我按照以下步骤使其正常运行:https://dev59.com/wVYN5IYBdhLWcg3wJFX8#47890273 - cwash
如何针对通过TCP连接的远程客户端实现此操作? https://stackoverflow.com/questions/61569297/golang-simple-server-with-interactive-prompt - manpatha

29

刚刚在 #go-nuts 邮件列表中看到了一封邮件,有个人编写了一个非常简单的 Go 语言包可供使用。您可以在这里找到它:https://github.com/howeyc/gopass

大致就是这样:

package main

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

func main() {
    fmt.Printf("Password: ")
    pass := gopass.GetPasswd()
    // Do something with pass
}

7
注意,此软件包使用来自Go作者golang.org/x/crypto/ssh/terminal软件包的terminal.MakeRaw函数。如果需要,可以直接使用该函数。 - Dave C
11
哦,还有terminal.ReadPassword - Dave C

26

自 Go v1.11 开始,官方推出了一个包golang.org/x/term,用于替换已停用的crypto/ssh/terminal。它具有许多功能,其中包括函数term.ReadPassword

使用示例:

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)
}

感谢您提供最新的回答! - T.S.
1
最新的答案 - hleb.albau
syscall.Stdin周围的int是多余的,它已经是一个int了。 - robvdl

11

我有一个类似的用例,以下代码片段对我很有效。如果你仍然卡在这里,请随意尝试。

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)
}

当然,你需要事先使用 go get 安装终端包。


11
在 Windows 上,我遇到了“句柄无效”的错误并发生了崩溃。必须获取标准输入的实际文件描述符:terminal.ReadPassword(int(os.Stdin.Fd())) - captncraig

7

您可以通过执行stty -echo来关闭回显,然后在读取密码后执行stty echo来打开回显。


(注:此段内容为关于如何在IT技术中关闭和打开密码回显的方法)

1
感谢提供使用stty的线索(我比*nix更熟悉Windows)。请注意,在我提供的源代码解决方案中,我需要特别使用Go的ForkExec()调用。普通的Exec()调用会将当前进程替换为stty进程。 - RogerV
1
Go语言中的ForkExec()调用现在已经在syscall包中,且少了两个参数。 - RogerV
1
现在有更好的方法 - 请查看下面的我的答案 - rustyx

5
以下是您可能会发现有用的使用Go1.6.2开发的解决方案。
它仅使用以下标准包:bufio,fmt,os,strings和syscall。更具体地说,它使用syscall.ForkExec()和syscall.Wait4()来调用stty以禁用/启用终端回显。
我已在Linux和BSD(Mac)上进行了测试。它不适用于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)
}

2

需要通过Go ForkExec()函数启动stty:

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
}

代码无法在go 1.10.3下构建。其中错误之一是undefined os.Error,可以通过将os.Error更改为error来修复,类似于https://github.com/imbc/go_starter_package/issues/1。看起来有很多Golang的变化。虽然感谢分享。 - minghua

2

这里是针对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
    }
}

所以要使用它:

fmt.Print("password: ")

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

TCGETS系统调用是Linux特有的。在OSX和Windows中有不同的系统调用值。


1
Go语言作者的golang.org/x/crypto/ssh/terminal包可以做到这一点,而且处理了Go支持的所有操作系统。(例如,在BSD上使用syscall.TIOCSETA而不是syscall.TCSETS,在Windows上使用kernel32.dll)。 - Dave C

1

0
关闭打字回显,输入完成后再开启回显。在Unix上可以找到不使用第三方库的方法,但在Windows上比较困难。您可以通过使用Windows的kernel32.dll中的SetConsoleMode方法来实现,参考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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接