MySQL账号密码严禁硬编码,必须通过环境变量或加密配置文件管理;环境变量适用于容器化部署,加密配置文件适合遗留系统,二者均需配合权限控制与密钥轮换。

MySQL 应用账号密码不能硬编码在代码里
硬编码 password='123456' 是最常见也最危险的做法——Git 提交、日志打印、进程列表(ps aux)都可能直接暴露密码。哪怕加了 .gitignore,本地开发环境的配置文件也常被误传或共享。真实线上事故里,80% 的数据库泄露起点是某份被提交的 config.py 或 application.yml。
必须把密码移出源码。可行路径只有两条:环境变量、加密配置文件。别无他选,也没必要搞自定义密钥轮转或服务端凭据管理——对中小项目过度设计反而增加维护成本。
- 环境变量适合容器化部署(Docker/K8s)、CI/CD 流水线,运维可控,但本地开发需统一约定加载方式
- 加密配置文件适合遗留系统改造、或需要离线部署的场景,但必须确保解密密钥不和配置文件放一起
- 二者都不解决“谁有权限读取环境变量/密钥文件”这一根本问题,所以
/etc/passwd权限、~/.my.cnf的chmod 600必须同步检查
用环境变量传 MySQL 密码时,os.getenv() 不够用
Python 的 os.getenv('DB_PASSWORD') 看似简单,但实际会遇到三类问题:空值未校验、类型混淆(比如密码含 $ 被 shell 展开)、多环境覆盖混乱。Node.js 的 process.env.DB_PASSWORD 同理,且容易在 .env 文件里误写成 DB_PASSWORD = "xxx"(等号前后空格导致值开头带空格)。
正确做法是封装一层校验逻辑,而不是裸调用 getenv:
import os
def get_db_credential(key, required=True):
val = os.getenv(key)
if required and not val:
raise ValueError(f"Missing required env var: {key}")
return val.strip() if isinstance(val, str) else val
<p>DB_HOST = get_db_credential("DB_HOST")
DB_PASSWORD = get_db_credential("DB_PASSWORD")- Docker 运行时用
-e DB_PASSWORD=xxx,**不要**用--env-file加载含明文密码的文件 - Kubernetes Secret 要挂载为环境变量,而非卷;否则容器内仍可能通过
cat /mnt/secret/password读到 - 本地开发用
direnv或python-dotenv加载.env,但该文件必须加入.gitignore,且禁止提交到仓库
~/.my.cnf 加密前先确认 MySQL 客户端是否真在用它
很多人以为只要写了 [client] 段就自动生效,其实不然。MySQL 命令行客户端(mysql)、mysqldump 默认读取,但 Python 的 pymysql、mysql-connector-python 和 Go 的 go-sql-driver/mysql **默认不读** ~/.my.cnf ——它们压根不解析这个文件。强行依赖会导致本地能连、程序连不上。
如果坚持用 ~/.my.cnf,必须满足两个条件:
- 应用使用支持该文件的驱动,例如
mysqlclient(Python)或启用readDefaultFile选项的mysql-connector-python - 文件本身需加密,推荐用
gpg --cipher-algo AES256 -c ~/.my.cnf,解密后仅内存中存在明文,脚本执行完立即shred临时文件 - 加密密钥绝不能写死在启动脚本里,应由运维人员手动输入或从硬件安全模块(HSM)获取
加密配置文件时,AES-GCM 比 base64 + 硬编码密钥强在哪
有人用 base64 编码密码再存进 JSON,还觉得“比明文强”。这等于锁门只用透明胶带——反编译、内存 dump、日志输出都能还原。真正有效的加密必须满足:密文不可逆推、每次加密结果不同、带完整性校验。
选 AES-GCM(不是 ECB/CBC)的原因很实在:
- AES-GCM 输出含认证标签(authentication tag),解密时自动校验是否被篡改,避免“密码错但连上了”的诡异行为
- 需要随机 nonce,所以同一密码每次加密结果不同,防止重放攻击或统计分析
- Python 示例(用
cryptography库):
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding from os import urandom <p>key = b'32-byte-key-must-be-exactly-this-long' nonce = urandom(12) # GCM 标准 nonce 长度 cipher = Cipher(algorithms.AES(key), modes.GCM(nonce)) encryptor = cipher.encryptor() ciphertext = encryptor.update(b'my_secret_pass') + encryptor.finalize()</p><h1>存储 nonce + ciphertext + encryptor.tag(三者缺一不可)
注意:密钥 key 本身不能出现在代码或配置里,得从外部注入;nonce 和 tag 必须和密文一起持久化,少一个就解不了。
最常被跳过的一步是密钥生命周期管理——上线后没人记得三个月换一次密钥,结果加密形同虚设。这事没法自动化,得靠运维排期和告警。


















