因为fmt.scanln会明文回显密码且存入shell历史,无法关闭终端回显;安全方案须用golang.org/x/term.readpassword,它跨平台禁用回显、返回[]byte并自动处理icanon/echo标志。

为什么 fmt.Scanln 不能安全读密码
直接用 fmt.Scanln 或 bufio.NewReader(os.Stdin).ReadString('\n') 读密码,密码会明文回显在终端上,且被 shell 历史记录(如 bash_history)捕获。这不是“没隐藏”,而是根本没绕过标准输入的行缓冲和回显机制。
真正要解决的是两件事:关掉终端回显 + 避免换行符干扰输入内容。
- Unix/Linux/macOS 下靠
syscall.Syscall调用ioctl关闭ICANON和ECHO标志 - Windows 下需用
golang.org/x/sys/windows调用GetStdHandle+SetConsoleMode - 跨平台统一处理?别自己硬写——直接用成熟库
推荐方案:用 golang.org/x/term(Go 1.19+ 内置)
golang.org/x/term 是官方维护的终端交互包,ReadPassword 函数专为密码输入设计,自动处理平台差异、信号中断、Ctrl+C 行为,并返回 []byte(注意不是 string)。
示例:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
package main
import (
"fmt"
"golang.org/x/term"
"os"
)
func main() {
fmt.Print("Password: ")
pwd, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
fmt.Println("\nReceived", len(pwd), "bytes")
}
- 必须传入
os.Stdin.Fd(),不能传os.Stdin本身 - 返回值是
[]byte,若需string可用string(pwd),但注意敏感数据应尽快清零:for i := range pwd { pwd[i] = 0 } - 按 Enter 后才返回;Ctrl+C 会返回
interrupt错误,需捕获处理
旧 Go 版本(
若无法升级 Go,或需支持非标准输入流(如重定向 stdin)、带提示符擦除、超时控制等,可考虑 github.com/howeyc/gopass(已归档但稳定)或 github.com/AlecAivazis/survey/v2(交互式问卷风格)。
-
gopass的GetPasswd返回[]byte,用法接近term.ReadPassword,但不依赖新标准库 -
survey.Password支持错误重试、掩码字符自定义(如*)、上下文取消,适合 CLI 工具而非脚本 - 切勿用
os/exec.Command("stty", "-echo")拼接系统命令——易被注入、不可靠、难清理
常见坑与注意事项
密码输入看似简单,但几个点容易出问题:
- 在容器或 CI 环境中运行时,
os.Stdin.Fd()可能不是真实 TTY,term.ReadPassword会报invalid argument;应先用term.IsTerminal(os.Stdin.Fd())判断 - 测试时若 stdin 被重定向(如
echo "123" | ./app),ReadPassword会立即失败,需 mock 或跳过该路径 - Windows 上某些终端(如 VS Code 集成终端)可能不完全兼容,建议加 fallback 提示:“请手动输入并确认”
- 别把密码存进
string后长期持有——Go 的string不可变,GC 前内存里可能残留多份,优先用[]byte并及时zero
最麻烦的从来不是“怎么读”,而是“读完之后怎么不留下痕迹”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










