在公共网络上提交含明文密码的代码有多危险?如何安全管理代码中的密码?
通过正确配置的 HTTPS 或 SSH 推送代码时,公共 Wi-Fi 上的旁观者通常无法直接读到文件中的密码。不过,无论使用什么网络,把真实密码提交到 Git 都会留下持续的泄露风险。应尽快将密码移出版本控制;已经推送过的有效凭据,应按可能泄露处理并安排轮换。
仅凭使用咖啡馆网络、将代码托管在 Bitbucket Cloud 或 GitHub,还无法判断实际风险。需要检查传输协议、证书或主机密钥校验是否正常,以及仓库的访问权限。
公共网络会增加哪些风险?
git commit 和 git push 的作用不同:
git commit通常只把内容写入本地仓库。git push会把提交发送到远程仓库。根据你的选择,SourceTree 也可能在一次操作中完成提交和推送。
SourceTree 是客户端,通信是否受到保护取决于实际使用的协议和配置。可以在仓库设置中查看远程地址,也可以运行:
git remote -v
| 远程地址形式 | 传输保护与检查重点 |
|---|---|
https://... | TLS 加密传输;必须正常校验服务器证书 |
git@host:... 或 ssh://... | SSH 加密传输;必须核验服务器主机密钥 |
http://... | 没有 TLS 保护,不应用于传输敏感内容 |
git://... | 协议本身不提供加密保护,不适合传输敏感内容 |
在证书校验正常的 HTTPS 连接中,即使文件里写着明文密码,网络上传输的也是加密数据。文件如何存储与传输是否加密是两回事。
公共网络上的攻击者可能冒充热点、操纵 DNS,或诱导你访问假网站。HTTPS 和 SSH 的身份校验可以抵御其中一部分攻击,前提是没有跳过校验:
- 不要为了修复连接错误而关闭 TLS 证书验证,例如设置
http.sslVerify=false。 - SSH 首次连接时,应通过可信渠道核对主机密钥指纹;遇到主机密钥变化警告时,先查明原因。
- 遵守公司的远程办公和 VPN 要求。VPN 可以保护设备到 VPN 出口之间的流量,但无法消除密码保存在仓库中的风险。
如果恶意软件已经控制了笔记本,这些保护也无法阻止它读取本地配置文件。
密码进入仓库后,风险会持续多久?
Git 会保留历史版本。即使今天删掉配置中的密码并重新提交,旧提交中的密码通常仍然存在。
真实凭据还可能进入其他人的克隆、备份、CI 工作目录、构建产物或日志。私有仓库能限制访问范围,但有权读取代码的人和工具也可能取得这些密码。一旦代码托管账户、访问令牌或某台开发机失陷,影响还可能扩大到这些密码所连接的服务。
即使云端平台正常加密传输和存储数据,也不能把源代码仓库当作密码库。尤其应避免让代码读取权限同时带来生产数据库的访问权限。
已经提交过密码,应怎样处理?
如果密码只存在于尚未推送的本地提交中,可以先修正本地历史,确认待推送内容已不含密码,再推送。如果密码已经离开受控环境,例如进入远程仓库、共享补丁或日志,就应评估暴露范围。
对于已经推送过的有效密码,建议按以下顺序处理:
- 轮换或撤销凭据。根据服务支持的方式创建替代凭据,更新应用并验证连接,然后撤销旧凭据。如果已有滥用迹象,应按公司的事件响应流程优先阻断访问。
- 检查仓库访问权限、相关服务的访问记录,以及 CI 日志和构建产物,排查密码是否进一步传播或遭到异常使用。
- 修改应用的配置方式,让后续部署从仓库外获取凭据。
- 按需清理 Git 历史。可搜索
git-filter-repo、BFG Repo-Cleaner,并与协作者协调历史重写和重新同步。
清理历史无法让已经复制出去的密码失效,因此仍需轮换凭据。删除远程分支也不代表所有副本都已消失。
密码应该放在哪里?
代码中保留配置结构和获取凭据的逻辑,真实凭据由运行环境提供。具体方式可以根据项目规模选择:
| 场景 | 可考虑的方式 | 搜索关键词 |
|---|---|---|
| 本地开发 | 使用仓库外的配置文件并限制读取权限,也可使用操作系统凭据存储 | macOS Keychain、Windows Credential Manager、Secret Service |
| CI/CD | 使用流水线的秘密变量或秘密存储功能,仅向必要任务开放 | CI/CD secrets、protected variables |
| 服务器或云环境 | 由应用身份访问集中式秘密管理服务 | HashiCorp Vault、AWS Secrets Manager、Azure Key Vault、Google Cloud Secret Manager |
| 目标服务支持身份认证 | 使用工作负载身份或短期令牌,减少长期密码的使用 | workload identity、managed identity、short-lived credentials |
环境变量是常见的凭据注入方式,本身并不会加密密码。调试输出、进程转储或具备足够权限的进程仍可能暴露或读取变量值。也可以使用受权限保护的挂载文件,具体取决于部署平台。
暂时无法接入秘密管理服务时,可以先把真实配置移出仓库、限制读取权限,并为开发、测试和生产环境使用不同凭据,以减少暴露范围。服务账户只应拥有应用实际需要的权限。
如果要加密配置文件,还需要确定解密密钥放在哪里。把密文和解密密钥一起提交,保护作用很有限。团队确实需要在 Git 中保存加密配置时,可以了解 SOPS、age 等工具,并单独设计密钥分发和访问控制。
Java 的 properties 文件可以怎样改?
可以继续使用 properties 文件,在仓库中保留一个不含真实凭据的模板,例如:
# config.example.properties
db.url=jdbc:postgresql://localhost:5432/app
db.user=
db.password=
真实配置由开发者或部署系统在仓库外提供。如果使用项目目录中的本地配置文件,应将其加入 .gitignore:
/config.local.properties
/.env
如果文件已经被 Git 跟踪,还需要停止跟踪:
git rm --cached -- config.local.properties
该命令会保留工作目录中的文件,并暂存从版本控制中移除该文件的变更。.gitignore 不会清除历史版本,也不会自动停止跟踪已有文件。
另一种简单的方式是让 Java 从环境变量读取凭据:
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException(
"Missing required environment variable: " + name
);
}
return value;
}
// 在建立连接的代码中读取,随后传给相应客户端 API。
String dbUser = requireEnv("APP_DB_USER");
String dbPassword = requireEnv("APP_DB_PASSWORD");
代码没有对密码调用 trim(),因为空格可能是密码的一部分。异常信息只包含变量名,不输出变量值。环境变量应由受控运行环境注入,避免把真实密码写入会被提交的启动脚本或留在终端历史中。
标准的 java.util.Properties 不会自动把 ${APP_DB_PASSWORD} 替换成环境变量值。若要使用占位符,需要确认框架是否支持,或显式编写读取逻辑。
可以进一步搜索 System.getenv 和秘密管理服务的 Java SDK。java.security.KeyStore 可用于管理某些密钥和证书,但要用它安全保存应用密码,仍需额外设计;访问它所需的凭据也要妥善管理。
C++、PHP 是否需要专门的库?
读取外部配置通常可以使用语言内置功能。接入操作系统凭据存储、秘密管理服务或工作负载身份时,再使用相应平台的 API 或 SDK。
- Java:
System.getenv()、java.util.Properties。 - C++:
std::getenv(),以及目标操作系统或秘密管理服务的 API、SDK。 - PHP:
getenv()、部署平台的秘密注入机制,以及秘密管理服务的 SDK。真实配置文件还应放在 Web 根目录之外,避免被直接下载。
无论使用哪种语言,都应避免在源码中硬编码密码,也不要将密码输出到日志、异常信息或命令行参数中。.env 只是配置文件约定,本身没有加密能力。
应用使用的服务密码与网站用户的登录密码,处理方式也不同:
- 应用连接数据库或外部服务使用的密码,通常必须能在运行时取出,或由令牌、身份认证替代。
- 网站用户登录密码通常应保存为带盐的密码哈希。可搜索 Argon2id、scrypt、bcrypt,并使用成熟实现;PHP 可了解
password_hash()和password_verify()。
把数据库连接密码做成单向哈希后,应用通常就无法用它登录数据库,因此不能直接套用用户登录密码的存储方法来管理服务凭据。
还可以加入 Gitleaks、TruffleHog 等秘密扫描工具,在本地提交检查和 CI 中运行。扫描可能漏报,尤其是自定义格式的密码,仍需配合代码审查和凭据轮换机制。
备注:内容仅供参考。
