用时间戳和 HMAC 实现一次性密码:构建安全、无明文的部署认证机制

在自动化部署中,明文密码常常是不可避免的风险点。尤其是在使用如 rsync、SFTP 等基于静态密码验证的工具时,部署脚本中往往不得不将密码暴露在 CI 系统或环境变量中。这种方式虽然便捷,但却带来了显著的安全隐患。

本文将介绍一种基于 HMAC 和 UTC 时间戳的动态认证方案。该机制无需存储明文密码,每次自动生成短时有效的一次性密码,可用于 CI/CD 流水线中的自动部署任务,并且对现有系统结构几乎无侵入。

1. 背景:自动化部署中的明文密码困境

自动部署系统中,我们通常会使用 rsyncscpftp 将构建产物上传至远程服务器。

为了实现无人工干预的部署,这些命令常常需要配合密码认证。最常见的做法是:

  • 将密码写入 CI Secrets 或脚本中
  • 配置 --password-file 或环境变量传递密码

这种方式虽然方便,但一旦 Secrets 泄露或日志配置不当,就可能导致密码暴露,造成严重后果。

2. 灵感来源:TOTP 与 HMAC

本方案受 TOTP(Time-based One-time Password)机制启发。

TOTP 使用时间戳和一个共享密钥,通过 HMAC 生成短效一次性验证码。这种方式广泛用于二步验证。

而在部署场景中,我们可以借鉴这一思路,使用 Bash 脚本 + openssl 实现一个轻量版本:

  • 密钥: 固定字符串 + 时间戳 + 用户名
  • 输入: 用户名本身
  • 输出: HMAC-SHA256 值,作为密码

3. 实现机制:生成一次性密码

示例代码如下:

1
2
3
4
5
USER="user1"
TIMESTAMP=$(( $(date -u +%s) / 300 * 300 ))
HMAC_KEY="${USER}${TIMESTAMP}SALT"

PASSWORD=$(echo -n "$USER" | openssl dgst -sha256 -hmac "$HMAC_KEY" | awk '{print $2}')

每 5 分钟会生成一组唯一的密码,且仅对当前用户名有效。

机制特点:

  • 每个用户密码不同(基于用户名)
  • 密码每 5 分钟变一次,过期即失效
  • 不保存明文密码,可重复计算
  • 可在客户端(CI)和服务端同步生成

4. 服务端实践:动态生成 rsync 密码文件

在服务器上,可使用如下脚本,每 5 分钟生成一次密码文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
USERS=("user1" "user2" "...")
TIMESTAMP=$(( $(date -u +%s) / 300 * 300 ))
SECRETS_FILE="/path/to/rsyncd.secrets"

> "$SECRETS_FILE"

for USER in "${USERS[@]}"; do
HMAC_KEY="${USER}${TIMESTAMP}SALT"
SECRET=$(echo -n "$USER" | openssl dgst -sha256 -hmac "$HMAC_KEY" | awk '{print $2}')
echo "$USER:$SECRET" >> "$SECRETS_FILE"
done

chmod 600 "$SECRETS_FILE"

并配置 rsyncd.conf 使用该文件:

1
2
auth users = user1,user2
secrets file = /path/to/rsyncd.secrets

搭配 cron 每 5 分钟自动刷新即可:

1
*/5 * * * * /path/to/generate_tmp_password.sh

5. 可扩展性:不仅限于 rsync

虽然这套机制是为 rsync 场景设计的,但只要目标系统支持密码认证,它就能工作。

适用场景包括:

  • SFTP(可配合 expect 脚本)
  • HTTP Webhook 验签
  • 临时 API Token
  • 内网机器之间的定时传输任务

你也可以加入:

  • 多时间窗口容错(±1 时间片)
  • 用户独立密钥支持
  • HMAC 结果截断(如只取前 8~12 位)以便人工使用

6. 总结与下一步

本文介绍了一种轻量、安全的动态密码方案,通过时间戳 + HMAC 在客户端和服务端分别生成同步密码,避免明文暴露。

在下一篇文章中,我们将介绍如何将此机制接入 GitHub Actions,结合 Secrets 和自动构建流程,实现真正“自动化但不裸奔”的安全部署。