🔐 三层密钥体系
PasswordReset v2.0 采用 三层密钥体系 确保密码从生成到存储再到使用的全链路安全。
架构总览
┌──────────────────────────────────────────────────────────────┐
│ 层1: 内置私钥 (BUILTIN_KEY) │
│ 位置: 程序二进制中硬编码 (编译时注入) │
│ 长度: 32 字节 (256 位) │
│ 作用: 加密保护用户的"安全key" │
│ 注入: go build -ldflags "-X ...BuiltinKeyHex=..." │
├──────────────────────────────────────────────────────────────┤
│ 层2: 用户安全key (USER_SECRET_KEY) │
│ 位置: /etc/PasswordReset/.secret_key.enc │
│ 加密: AES-256-GCM + 内置私钥 │
│ 来源: PASSWORD_RESET_SECRET_KEY 环境变量 │
│ 长度: 至少 8 字符 (建议 16+) │
├──────────────────────────────────────────────────────────────┤
│ 层3: 组件加密key (COMPONENT_KEY) │
│ 生成: SHA256(安全key + 日期YYYYMMDD) │
│ 存储: 不持久化, 每次运行时在内存中派生 │
│ 用途: AES-256-GCM 加密/解密密码组件 (密码后缀) │
└──────────────────────────────────────────────────────────────┘数据流详解
加密(初始化时)
管理员输入:
安全key (PASSWORD_RESET_SECRET_KEY) ──→ 内置私钥 AES-256-GCM 加密
↓
.secret_key.enc (落盘)
密码组件 "MySuffix" ──→ SHA256(安全key + 今天日期)
↓
AES-256-GCM 加密
↓
ENC:20260525:base64... (存储于配置)解密(每日运行时)
读取配置 ENC:20260525:base64...
│
├── 提取嵌入日期: 20260525
├── 派生解密key: SHA256(安全key + "20260525")
├── AES-256-GCM 解密 → 密码组件 "MySuffix"
│
├── 当天日期: 20260526
├── 最终密码: 20260526MySuffix
│
├── 设置系统密码 (chpasswd)
│
├── 派生新key: SHA256(安全key + "20260526")
├── AES-256-GCM 重新加密组件
└── 存储 ENC:20260526:new_base64... (更新配置)安全属性
1. 全程无明文
| 环节 | 安全措施 |
|---|---|
| CLI 参数 | 仅支持 -password-file 或环境变量,无明文参数 |
| 环境变量 | 密码组件以 ENC: 格式传递 |
| 配置文件 | 两层 AES-256-GCM 加密(外层 master key + 内层日期key) |
| 内存 | 解密后使用完毕立即释放 |
| 进程列表 | 密码通过 chpasswd stdin 管道传递,不在进程列表暴露 |
2. 密钥隔离
- 即使攻击者获得二进制文件,也无法破解内置私钥(需逆向)
- 即使攻击者获得
.secret_key.enc,没有内置私钥也无法解密 - 即使攻击者获得安全key,没有配置文件中的 ENC 串也无法得到密码
- 每日派生key不同,历史加密数据不受未来密钥泄露影响
3. 管理员可恢复
管理员只需记住:
- 安全key (
PASSWORD_RESET_SECRET_KEY) - 密码组件 (加密时的 suffix)
即可推算任意一天的系统密码:YYYYMMDD + 组件
4. 攻击模型
| 攻击场景 | 是否可破解 |
|---|---|
| 仅获取二进制 | ❌ 内置私钥需逆向工程 |
仅获取 .secret_key.enc | ❌ 无内置私钥 |
| 仅获取配置文件 | ❌ 无安全key |
| 获取二进制 + 配置文件 | ❌ 仍需安全key |
| 获取安全key | ⚠️ 需配合配置文件中的 ENC 串 |
| 获取全部(二进制 + 配置文件 + .secret_key.enc) | ❌ 每日派生key不同,历史数据仍有保护 |