fmt.scanln不能读密码因其明文回显且存入shell历史;安全方案需满足不回显、不存历史、防信号截获;推荐用golang.org/x/term.readpassword,注意清内存、终端检测、信号恢复与上下文超时。

为什么 fmt.Scanln 不能读密码
因为 fmt.Scanln 会把输入明文回显到终端,且内容会留在 shell 历史里。用户输密码时看到自己打的字符,等于直接暴露——这不是“不安全”,是根本没做防护。
真正可用的方案得满足三点:不回显、不存入历史、不被信号中断截获。Go 标准库不提供跨平台终端掩码读取,得靠第三方或系统调用。
用 golang.org/x/term.ReadPassword 是最稳选择
这是 Go 官方维护的扩展包,底层调用各平台原生 API(Windows 的 GetStdHandle + SetConsoleMode,Unix 的 ioctl + tcsetattr),比手撸 syscall 更可靠。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 安装:
go get golang.org/x/term - 必须从
os.Stdin读,不能传文件描述符或自定义 reader - 返回的是
[]byte,别直接转string存——用完立刻bytes.Zero清内存 - 错误只可能是
io.ErrUnexpectedEOF或底层 syscall 失败,不抛出明文提示
func readSecret() (string, error) {
fmt.Print("Password: ")
pwd, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
defer func() { for i := range pwd { pwd[i] = 0 } }() // 立刻清空
return string(pwd), nil
}
Windows 下绕过 term.ReadPassword 的坑
某些 Windows 终端(如 VS Code 集成终端、旧版 PowerShell)可能不支持 term.ReadPassword 的原始模式切换,报错 invalid argument 或直接卡住。
- 先检查
os.Getenv("TERM")是否为空,再试term.IsTerminal - 降级 fallback:用
bufio.NewReader(os.Stdin).ReadBytes('\n')+ 手动擦除行首(不推荐,仅应急) - 更稳妥的做法是加一层检测:
if runtime.GOOS == "windows" && !term.IsTerminal(int(os.Stdin.Fd())),则提示用户换 CMD 或 PowerShell
别忽略信号和上下文取消
用户按 Ctrl+C 时,term.ReadPassword 默认会返回 interrupt 错误,但不会自动恢复终端状态——可能导致后续输出乱码。
- 务必用
signal.Notify捕获os.Interrupt,并在 defer 中调用term.Restore - 如果函数要支持超时,不能靠
time.AfterFunc杀 goroutine,得用context.WithTimeout+select包裹整个读取逻辑 - 注意:
term.Restore必须传入term.MakeRaw返回的原始 state,不是随便 new 一个
隐秘输入真正的复杂点不在读取本身,而在终端状态的进出平衡——漏掉一次 Restore,整个 CLI 就会变成乱码地狱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










