密码安全实践:从明文存储到凭据隔离
一、从一个真实的疏忽说起
前阵子我在整理一个项目的接口说明文档,准备归档时才发现——那份文档里明明白白写着接口的鉴权 Token,是完整可用的原值。
文档本身没问题,写得挺规范:鉴权方式、请求示例、字段说明一应俱全。问题在于,为了让"示例"足够真实,Token 被原样贴了进去。而这份文档当时正躺在交流文件里,随时可能被转发出去。
发现之后当然立刻处理了。但这件事让我意识到一个更普遍的问题:绝大多数人并不是"不在乎安全",而是根本没意识到自己已经把凭据暴露出去了。
贴 Token 的时候想的是"方便对方调用",写密码的时候想的是"先跑通再说"。这些想法本身没错,出问题的是没有人在后面把它们收回来。
这篇文章想聊的就是这件事——密码和凭据到底该怎么管,分个人和开发两个场景来说,都是能立刻上手的做法。
二、先分清三个层次
"密码安全"这个词太大了,容易一谈就变成泛泛而谈。实际上它至少分三个层次,每个层次的问题和对策都不一样。

| 层次 | 典型对象 | 核心风险 |
|---|---|---|
| 个人账号密码 | 邮箱、社交、网银、各种网站 | 撞库、钓鱼、复用导致一破全破 |
| 开发中的凭据 | API Key、数据库密码、Token | 写进代码、提交进版本库、贴进文档 |
| 服务间凭据 | 多服务/多账户共享的密钥 | 权限过大、无法追溯、一泄漏影响全局 |
很多事故的根源,是把这三个层次当成一件事来处理。 比如用管理个人密码的思路去管理服务器密钥——记在一个备忘录里,谁要就发给谁。个人场景这么做最多丢个账号,服务场景这么做可能是整个系统失守。
三、六个最常见的错误
3.1 明文写在文档、代码或聊天记录里
这是最常见也最容易被忽视的。表现包括:写死在代码常量里、贴在接口文档的示例中、留在聊天的历史记录里、存在桌面的 txt 文件里。
共同点是——它们都脱离了你的控制范围。文档会被转发,代码会进版本库,聊天记录会同步到云端,桌面文件会被同步盘带走。
3.2 多系统复用同一个密码
很多人觉得"我密码设得够复杂,复用没关系"。但问题是:你保证自己不被拖库,保证不了每一个你注册过的网站都不被拖库。
任何一个你用过的小网站出事,攻击者就拿这份账号密码去试你的邮箱、网银、社交账号——这就是"撞库"。复用的代价不是多丢一个账号,而是一次全丢。
3.3 弱密码加规律化
用生日、手机号、姓名拼音,或者"某某网站123"这种带规律变体的密码,在自动化工具面前基本等于没有。
还有一个反直觉的点:强制的大小写+数字+符号组合,反而不如一个很长的纯小写短语安全。 复杂度规则会逼着人写出 Abc123! 这种既难记又容易猜的密码。
3.4 提交进了版本库
这是开发场景里最危险的一种。因为版本库有历史记录——你把密钥从代码里删掉,历史提交里还原样留着。
很多人删掉之后以为没事了,实际上只要有人 clone 过,或者仓库曾经公开过,那个密钥就等于已经泄露了。
3.5 权限给得太大
为了方便,经常出现"一个 Token 管所有"、"给个管理员权限省得来回问"的情况。
结果是一旦这把钥匙丢了,丢的不是一个房间,是整栋楼。
3.6 长期不更换
密钥设好之后就再也没动过,几年不换。时间越长,暴露面越大——某次截图、某次日志、某次临时的共享,都可能已经把它带出去了,而你永远不知道。
四、个人层面:三件立刻能做的事
① 装一个密码管理器。
这是投入产出比最高的一件事。装好之后你只需要记住一个主密码,其余全部交给它生成和填写。带来的直接好处是:你终于可以用上几十位随机密码,并且每个网站都不一样。
3.2 和 3.3 两个问题,会因为这一个动作同时消失。
② 关键账号开启两步验证(2FA)。
尤其是邮箱——邮箱是所有账号的"总钥匙",因为几乎所有网站的密码重置都走邮箱。邮箱被攻破,等于其他账号也保不住。
优先开启顺序:邮箱 → 网银支付 → 社交账号 → 云盘。能用认证器 App 或硬件密钥的,不要用短信验证码(短信可被拦截和补卡攻击)。
③ 定期检查密码是否已泄露。
有一些公开的泄露查询服务,输入邮箱就能查这个账号是否出现在已知的数据泄露事件中。查到了就立刻改,并且改掉所有用了同一个密码的地方。
五、开发层面:凭据管理的正确姿势
这一节是这篇文章的重点,也是最容易出大事故的地方。

5.1 放哪里:配置文件 + 文件权限
基本做法是把凭据从代码里拿出来,放进独立的配置文件或环境变量文件:
# .env
DB_PASSWORD=xxxxxxxx
API_TOKEN=xxxxxxxx
然后立刻把它加进 .gitignore:
# .gitignore
.env
*.key
*credentials*
config.local.*
再加一道保险——把文件权限收紧:
chmod 600 .env # 只有属主可读写
ls -l .env # 确认权限是 -rw-------
这一步很关键但常被跳过。默认权限是 644(同机其他用户可读),在多用户环境下等于没保护。
5.2 已经提交了怎么办
这是很多人踩过的坑,处理顺序不能错:
- 第一件事是立刻轮换密钥,不是删历史。 只要提交过,就要假定它已经泄露。先去服务商后台把密钥作废、重新生成,这一步做完再谈其他
- 然后才清理版本库历史——改写历史、强制推送,把那个文件从所有提交中抹掉
- 通知协作者重新拉取——历史改写后,其他人的本地仓库会冲突,需要说明清楚
- 之后检查有没有被滥用——查服务商后台的调用记录、账单异常
顺序反了是最危险的:先花时间清理历史,密钥还挂在那里可用,攻击者该拿的早就拿到了。
5.3 权限怎么给:最小权限
几条实用原则:
- 按用途拆——读的密钥不要给写权限,测试环境的密钥绝不用在生产
- 按服务拆——每个服务一份独立凭据,出问题能定位到具体哪一个
- 设过期时间——能用临时凭据的就不用永久凭据
- 留审计记录——谁在什么时候用了它,要能查
5.4 别把凭据贴进任何"给人看"的东西里
回到开头那个故事。接口文档、Readme、演示代码、给同事的说明——这些都会流动出去。
写文档时用占位符就好:
X-Api-Token: <你的Token>
需要真的给对方用,就通过单独的、可控的渠道传递,而不是写在一个会被转发的文件里。
六、多服务场景:凭据隔离
这一节是前几年不太会遇到、但现在越来越常见的情况——同时运行着多个助手、机器人或自动化服务。
典型的做法是把技能、脚本、配置集中放在一个共享目录里,方便统一管理。这本身没问题,但有一条硬规矩:
含凭据的文件,一律不要放进共享目录。
原因很直白:共享目录里的东西,所有服务都能读到。你放的是自己用的邮箱密码,结果所有实例都拿到了它——包括那些本来不需要、也从来没打算给它这个权限的。
正确的做法是按归属拆开:
| 位置 | 放什么 | 可见范围 |
|---|---|---|
| 共享目录 | 通用能力、无凭据的公共脚本 | 所有服务可见 |
| 各自独立目录 | 含凭据的配置、业务专属脚本 | 仅该服务可见 |
这里的关键认知是:权限隔离应该靠结构来保证,而不是靠自觉。
"大家注意不要读别人的配置"——这种约定在压力下、在赶进度的时候,一定会被打破。但如果物理上就放在不同目录里、权限上就读不到,那就没有打破的可能。
七、一份可执行的检查清单
| 检查项 | 标准 |
|---|---|
| 版本库里有没有凭据 | 搜一遍仓库历史,确认没有密钥、密码、Token |
| .gitignore 是否覆盖 | 至少包含 .env、*.key、含 credentials 的文件 |
| 凭据文件权限 | -rw-------(600),不是 644 |
| 有没有文档里贴了真 Token | 全部替换为占位符 |
| 是否一码多用 | 每个服务、每个环境独立凭据 |
| 轮换周期 | 有明确周期,且泄露后立即轮换 |
| 关键账号 2FA | 邮箱、支付、云平台全部开启 |
| 个人密码是否复用 | 用密码管理器,全部唯一 |
| 泄露应急流程 | 第一动作是轮换,不是删历史 |
八、小结
密码安全这件事,难点从来不在技术,而在习惯。
没有哪种方案能一劳永逸。加密再强的系统,也拦不住把密钥贴进文档里这个动作。反过来,只要守住几条基本纪律,就能挡住绝大多数真实的攻击:
- 不写死——凭据永远不写进代码、文档和聊天记录
- 不复用——每个账号、每个服务、每个环境都用独立的
- 不上库——确认版本库里没有凭据,历史里也没有
- 不给大——最小权限,按用途拆分
- 不失察——定期轮换,泄露了立刻换(而不是先删历史)
安全不是一次性的配置,而是一套持续的动作。真正重要的不是"我配得多严密",而是"当有一天我意识到出问题时,我知道第一个动作该做什么"。
而对于大多数人来说,只要做到两件事就已经超过了平均水平:用一个密码管理器,把关键账号的两步验证打开。