跳到主要内容

git-crypt

USER

用 git-crypt 的 gnupg 最佳实践是什么?

ASSISTANT

使用 git-crypt 搭配 GnuPG (GPG) 是在团队中安全管理 Git 仓库中敏感信息(如密钥、配置文件)的常见方案。以下是一些公认的 最佳实践,可以帮助你安全又方便地部署与维护这种机制。


一、GPG 密钥管理最佳实践

  1. 为 git-crypt 使用专用的 GPG 密钥

    • 不建议直接使用个人签名或邮件用的 GPG 密钥来加解密仓库。
    • 建议为工作用途(甚至为每个团队/项目)生成专用的 GPG key,职责分离。
    • 例如:
      gpg --full-generate-key
      # Comment: git-crypt@yourproject
  2. 设定合理的密钥过期时间

    • 避免密钥长期有效;建议设置过期时间(例如 1 年),到期再延长:
      gpg --edit-key KEYID
      expire
      save
  3. 使用强算法

    • 生成 GPG key 时使用 RSA ≥ 4096 或 Curve25519 (Ed25519)。
    • 检查配置:
      gpg --version
      看是否支持这些曲线。
  4. 私钥妥善备份、安全保存

    • 私钥绝不应进入仓库。
    • 密钥应储存在:
      • 启用了磁盘加密的工作站,
      • 或使用 YubiKey/智能卡进行硬件保护。
  5. 使用 GPG agent 进行缓存

    • 避免频繁输入密码,可设置合理的 TTL。
    • 例如编辑 ~/.gnupg/gpg-agent.conf
      default-cache-ttl 600
      max-cache-ttl 7200
    • 然后 gpgconf --reload gpg-agent

二、git-crypt 使用与团队管理

  1. 使用 key 方式添加成员(推荐)

    • 每个成员用自己的 GPG 公钥加入:
      git-crypt add-gpg-user user@example.com
    • 这样 git-crypt 会创建新的加密 master key,并用每位成员的 GPG key 加密一份。
  2. 定期轮换成员密钥

    • 成员离开团队后,应:
      • .git-crypt/keys/ 中移除该成员的 key;
      • 用新 master key 重新加密 (即重新初始化 git-crypt,并重新授予所有现有的成员);
      • 这一步是必要的,因为 git-crypt 不支持动态撤销访问权限(旧密钥仍能解密旧数据)。
  3. 不要在仓库外泄露密钥文件夹

    • .git-crypt/ 目录是受控部分,不应手动编辑或上传到别处。
  4. 加密范围明确管理

    • 使用 .gitattributes 指定具体需要加密的文件或路径。
    • 举例:
      secrets/** filter=git-crypt diff=git-crypt
    • 不要一股脑加密整个仓库,以便非敏感部分可正常 diff、merge。
  5. 设置 CI/CD 系统

    • CI 运行环境不应直接使用你的私钥。
    • 可以考虑:
      • 在 CI 环境中使用对称密钥解密方式;
      • 或预置一个具有有限权限的 GPG key(仅解密构建所需文件)。
    • 避免把开发者主密钥放上 CI。

三、日常维护与安全审查

  1. 同步密钥状态文档

    • 保留一个密钥信任图(谁持有哪些 key),定期验证其有效性与过期时间。
  2. 启用 git-crypt status 定期检查

    • 发现未解密或未加密的文件及时修复。
  3. 注意远程仓库泄露

    • 虽然文件加密,但 .gitattributes、文件路径仍然可见。
    • 避免在路径、文件名中直接暴露敏感内容。
  4. 审计加密内容

    • 使用 git log -- .gitattributes 查看加密规则变动;
    • 使用 git lfs 等扩展不要混用 git-crypt。

四、小结(核心要点)

类别最佳实践
GPG 密钥使用专用 key、设置过期、安全存放
团队管理使用 add-gpg-user、成员离职后重新初始化
仓库策略.gitattributes 精准匹配、不要加密整个仓库
环境集成CI 使用有限权限 key 或对称方式
安全维护定期轮换密钥、检查状态、记录信任链

如果你希望我补一个 实际操作流程示例(例如从初始化仓库到添加用户、加密文件、在新机器上解密的完整步骤),我可以一步步帮你列出必要命令和注意事项。是否需要?

USER

在 git-crypt 中,gpg 只是用个人的公钥加密了真正用于加解密文件的对称密钥是吧?

ASSISTANT

是的,你理解完全正确 👍

git-crypt 的设计中,GPG 的作用只是保护对称密钥(symmetric key),而不是直接参与每个文件的加解密操作。

我们来详细分解一下背后的机制:


🔐 整体架构概念

  1. 核心密钥(repository master key)

    • 当你执行 git-crypt init 时,git-crypt 会生成一个随机的「主对称密钥」(通常称为 repo key 或者 symmetric key)。
    • 这个密钥负责实际加密、解密仓库中标记为敏感的文件。
    • 对称密钥意味着加密和解密使用的是同一个密钥。
  2. GPG 公钥用来保护这把主密钥

    • 当你执行:
      git-crypt add-gpg-user user@example.com
      git-crypt 会做以下几件事:
      1. 读取仓库中的对称主密钥;
      2. 用该用户的 GPG 公钥(非对称加密)加密这把对称主密钥;
      3. 把加密后的「repo key 副本」放到 .git-crypt/keys/ 目录里。
  3. 每个用户都有自己的一份加密副本

    • 团队里每个成员的公钥加密副本都存储在 .git-crypt/keys/<hash>.gpg
    • 当用户 clone 仓库并执行 git-crypt unlock 时:
      • 自己的 GPG 私钥会解密那份 repo key 副本;
      • git-crypt 就得到了对称密钥,从此可正常处理所有加密文件。
  4. 文件加密本身是对称的

    • 被配置在 .gitattributes 中的文件会用 repo key 对称加密(AES 等算法);
    • 因此,实际的数据操作完全不依赖 GPG,只用 symmetric key 即可快速读写。

🧩 这样设计的原因

  • 性能:GPG(非对称加密)速度比较慢,用它加每个文件不现实。
  • 访问控制:使用不同成员的公钥加密同一把 repo key,就能实现“多用户共享访问权”。
  • 密钥轮换灵活:成员离职、权限变动时,只需重新生成一把新的对称密钥并重新分发即可。

📜 示例 (简化逻辑)

伪流程如下:

repo_key = random_bytes(256-bit)

# 对文件加密
EncryptedFile = AES_encrypt(File, repo_key)

# 对 repo_key 进行成员加密
for user in [Alice, Bob]:
repo_key_for_user = GPG_encrypt(repo_key, user.pubkey)
store_in_repo(".git-crypt/keys/<user_id>.gpg", repo_key_for_user)

当 Alice 解密时:

repo_key = GPG_decrypt(repo_key_for_alice, alice.private_key)
File = AES_decrypt(EncryptedFile, repo_key)

✅ 小结

项目说明
GPG 的作用仅用于加密仓库的对称密钥(repo master key)
实际文件加密使用该对称密钥(AES 等)进行
每个用户一份独立的 repo key 副本,由个人公钥加密
优点性能高、访问控制清晰、撤销灵活

如果你想更深入了解「repo key」的存储格式,或如何安全地重新生成/轮换这把对称密钥,我可以接着说明内部文件结构和实际操作命令。是否想看这部分?

USER

你的逻辑好像有问题,有成员离职,即使要换对称密钥也需要把所有文件重新加密一遍,甚至历史文件还是会被离职成员带走

ASSISTANT

你指出的非常正确,这个地方确实容易被误解;你说的情况是事实。
我来给你更精确地梳理一下 git-crypt 的权限与密钥管理逻辑的现实限制。


✅ 实际机制(澄清)

git-crypt 在初始时生成的「repository master key」是一把单一的对称密钥,这个密钥之后就负责加密仓库里所有被标记的文件。

各个成员的 GPG 公钥仅仅用于解锁(解密)这把主对称密钥
因此,一旦成员获取了 repo key,就可以:

  • 解密仓库里所有当前和历史版本中被加密的文件;
  • 因为 Git 是版本控制系统,用户一旦有 repo key,并且能访问整个 Git 历史(哪怕离职后仍有仓库拷贝),他就可以解出那时所有加密过的 blob。

🚫 困境 1:成员离职后无法撤销其历史访问权

你说得非常对,这意味着:

  • 不能撤销过去的访问权限
    如果某人当时能读取仓库,他拥有 repo key,就能一直解密历史数据;
  • 即使后来删除他的 .git-crypt/keys/... 文件,对他本地已经解密的 repo key 毫无影响;
  • 想彻底阻止他访问后续的加密内容,必须重新生成一把新的 repo key,并重新加密敏感文件。

🔄 换密钥(轮换)的实际代价

  1. 重新初始化新的 repo key

    mv .git-crypt .git-crypt.old
    git-crypt init
    git-crypt add-gpg-user <现有成员公钥...>
  2. 重新加密文件
    因为所有文件之前都是使用旧的对称密钥加密的,要想用新密钥保护,就得让 Git 认作所有加密文件都更新过:

    git-crypt lock
    git-crypt unlock

    这会重新用新密钥加密那些受保护的路径。

  3. 提交、推送更新后的加密数据
    所有被加密的文件内容在 Git 层面都会显示为变化(因为密文变了),需要新的 commit。

→ 这相当于“轮换整仓库的密钥”,确实需要对仓库所有加密内容执行一次再加密。
但关注点是:这样可以防止离职成员解密轮换之后的新内容,没法剥夺其已拥有的历史版本的访问权。


🧩 如果想更细粒度控制访问的策略

git-crypt 本身是偏向“轻量共享密钥”模型的工具,并不是做「访问级别控制」的系统,所以它有这些先天局限。
常见替代或者补充方案包括:

方案优点缺点
每个敏感文件独立密钥(自行管理)可单独撤销访问手动复杂,难自动化
使用 git-secret同样 GPG 模型,更简单,但撤销同样困难概念类似
存储密钥到外部系统 (比如 HashiCorp Vault, SOPS+KMS)可以在服务端控制权限和轮换架构更重
在 CI 构建时从外部 Secrets Manager 拉取密钥无需把密钥进仓库增加部署依赖

🧭 总结

话题事实
GPG 公钥的作用仅用来加密 repo 对称密钥
撤销成员访问删除成员 key 文件不能撤销旧权限
阻止其访问后续内容需要重新生成对称密钥重新加密全部受保护文件
历史版本数据任何获得 repo key 的人永远可解密当时加密的历史内容
根本局限git-crypt ≠ 权限管控系统,它只是共享加密工具

如果你希望我帮你总结“如何在实践中组织安全分层(例如:让仓库分为公开部分 + 受限 secrets 仓库)以减少密钥轮换负担”的几种团队工作流,我也可以帮你画个建议结构图。需要吗?

USER

需要

ASSISTANT

很好 👍 这其实是许多团队在使用 git-crypt 或其它源码加密工具时最关键的一步:
如何在团队协作中设计一个可控、可轮换、低摩擦的安全架构

下面是一组现实可落地的组织与技术建议,可以算是 git-crypt 的「安全应用分层参考架构」。


🧱 一、分层思路概览

层级目标主要方式
公共层(Public Code)所有人共享与协作的源代码,不含敏感信息Git 存库、正常协作
受控 Secrets 层(Sensitive Config)团队少部分人可接触的密钥、配置、证书等单独 repo + git-crypt 加密
构建/部署层构建系统在需要时拉取 secretsCI/CD 结合密钥注入机制
生产运行层secrets 从 Secrets Manager 下发(不在 Git 中)Vault / AWS KMS / GCP Secret Manager 等

🗂️ 二、仓库结构建议

方案 1:分仓库(推荐)

把「代码」与「密钥」彻底分离。

your-app/
├── app/ ← 普通源码仓库(无密钥)
│ ├── src/
│ ├── Dockerfile
│ └── .git/
└── app-secrets.git/ ← 独立仓库,git-crypt 加密敏感文件
├── production/
├── staging/
└── .gitattributes
  • 普通开发者只需访问 app/ 仓库;
  • 仅发布或运维人员可以 clone app-secrets.gitgit-crypt unlock
  • CI/CD 只需在构建时访问受控 secrets 仓库;
  • 离职人员简单失效:撤销该仓库访问权限即可(不影响主项目协作)。

优点:

  • 物理隔离最彻底;
  • 密钥轮换不会影响主仓库;
  • 权限粒度细,可以靠 GitLab/GitHub 权限或 ACL 控制。

方案 2:单仓库 + 受限加密子目录

如果团队规模较小,可以在一个仓库里用 .gitattributes 严格定义加密目录:

project/
├── src/
├── config/
│ ├── secrets/ ← 加密的目录
│ │ *.yaml filter=git-crypt diff=git-crypt
│ ├── public.yml
│ └── README.md
└── .gitattributes

团队内部分角色:

角色拥有 GPG Key?能解密 secrets?
开发者
构建/CI 用户有限专用 key
发布管理员

限制:

  • 密钥轮换仍需要重新初始化 git-crypt 并重加密;
  • 离职后依然能解密历史版本;
  • 但至少普通开发者不会接触敏感目录。

方案 3:git-crypt + 云端 Secrets 管理混合

  1. 代码中存放的是加密后的包裹文件
  2. 在 CI/CD 流程中,通过云 Secrets 管理平台(如 AWS SSM / GCP Secret Manager / Vault)自动解密并生成真实文件;
  3. 部署完成后销毁中间文件。

适用于需要跨环境共享配置的企业环境。
例:

# 在 CI 步骤中
export SECRET=$(aws ssm get-parameter --name /project/db_password --with-decryption)
echo $SECRET > config/secrets/db_pw.txt

这样就不再依赖 git-crypt 来保存超级敏感数据,而是让 Secrets Manager 成为单一密钥来源。


🔑 三、密钥与权限管理建议

1. CI/CD 使用独立 GPG Key

  • CI 不使用个人开发者的主私钥;
  • 生成专用 GPG 密钥,仅能解密部署所需 secrets;
  • 这把 CI 密钥由管理员托管,过期时间可控(例如 6 个月)。

2. 用户加入/离职流程

  • 由密钥管理员统一执行:
    1. 对新成员执行 git-crypt add-gpg-user
    2. 离职后移除公钥,启动新一轮 repo key 轮换;
    3. 通知现有成员拉取新的 secrets 仓库版本。

3. 文档化密钥信任链

  • 维护 README 或 SECURITY.md,明确团队各角色的密钥持有与轮换周期;
  • 审计方便,也能协助安全评估。

🧾 四、推荐组合实践举例

场景推荐方案
小团队、非生产密钥单仓库 + git-crypt 子目录方案
中大型团队、安全要求高分仓库方案(主代码 / secrets)
企业级部署、需短期权限控制云 Secrets Manager + CI/CD 注入
短暂项目或开源需演示 encrypt 机制单 repo,简化 git-crypt 即可

🧭 五、架构总览(摘要)

               ┌────────────────────────┐
│ Git 主仓库 │
│ - 源代码 │
│ - 无敏感信息 │
└────────────┬───────────┘


┌────────────────────────┐
│ Secrets 仓库 / 层 │
│ - git-crypt 加密文件 │
│ - 限定成员访问 │
└────────────┬───────────┘
│(CI/CD 解密)

┌────────────────────────┐
│ 构建 / 部署环境 │
│ - 读取 secrets │
│ - 构建镜像或部署 │
└────────────┬───────────┘


┌────────────────────────┐
│ 生产 Secrets 管理系统 │
│ (Vault / AWS KMS等) │
└────────────────────────┘

✅ 总结要点

  • 代码和密钥逻辑层次拆分,用访问控制而不是仅靠加密隔离;
  • Git 层面隔离敏感文件(子目录或分仓库);
  • 使用 git-crypt 时:
    • 只为少部分关键成员加 GPG key;
    • 定期轮换 repo key;
    • 记录信任链和密钥到期时间;
  • 长期来看,用外部 Secrets 管理系统接管密钥会更可靠。

是否希望我接着拿其中一个方案(比如「两个仓库分离 + CI/CD 解密」)为模板,给出一个可用的实际命令和配置文件实例?

USER

还是不对啊,还是无法吊销旧成员对 secrets 仓库历史的访问

ASSISTANT

完全正确。你抓住了核心——Git‑crypt 是加密工具,不是权限撤销系统。只要某个成员在某一时刻拥有过仓库的对称密钥,他们就能:

  • 解密当前版本及当时所有历史版本的受保护文件;
  • 因为 Git 本身会保存所有历史 blob,git-crypt 并不会重写历史;
  • 删除 .git-crypt/keys/... 或禁用他们的公钥只会阻止他们获取“未来的新密钥”,无法剥夺他们手里已经拿到的旧密钥和旧仓库。

换句话说:

一旦密钥发出去,就无法从技术上收回历史访问权。


那能做什么?

只能做到向前吊销(forward revocation)

  1. 生成新的对称密钥(重新 git-crypt init)。
  2. 重新加密所有敏感文件
    这样以后提交的新密文都用新密钥。
  3. 从仓库访问层面撤销旧成员的访问权限
    • 禁止他们访问远程仓库(Git 服务器层)
    • 私有分发新的密钥给留任成员

但他们仍然能解出之前版本的所有敏感文件。
除非你重写 Git 历史去掉这些记录(例如 git filter-repo / git filter-branch 删除旧密文),仓库历史才不再有可解密的数据。这显然是高成本的。


安全设计的现实做法

  1. 不要把长期保密的数据放入 Git 历史。

    • 例如生产密码、长期证书、客户数据都不要入库。
    • 可以在构建时动态取用 Secrets Manager。
  2. 把 secrets 仓库视作一次性快照

    • 每次轮换主密钥时创建新 repo 或重写历史;
    • 旧仓库彻底归档或销毁。
  3. 在外部控制访问而不是依赖 Git

    • 使用云端 Secrets 服务(Vault、AWS KMS、GCP Secret Manager);
    • Git 里只存访问这些服务的模板或权限配置。

实际可操作的“强吊销方案”

目标方法代价
防止今后访问轮换 git‑crypt 主密钥,重加密文件中等
防止访问历史密文重写 Git 历史删除过去密文提交
防止任何未来访问敏感信息从仓库移除 secrets、使用外部 Secrets 服务推荐

所以总结:

  • 你指出的问题不存在“修复技巧”,这是 git-crypt 在设计模型上的限制;
  • 它只在“当前团队、稳定成员”场景下有效;
  • 若需要支持吊销、短期访问或动态密钥,必须借助外部 Secrets 系统。