OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 098

sops 原理:混合加密与接收者

理解密文版本管理、数据密钥与多接收者授权的边界。

所在专题SOPS 加密配置与密钥生命周期 ↗
约 7 分钟阅读开始阅读

sops 解决什么问题#

sops 不是「加密钱包/保险箱」,而是「git 友好的 secrets 版本控制方案」。 核心痛点:需要版本化或 review 的秘密配置可以以密文进入 git,而明文和解密密钥应留在仓库外。

围绕这个目标,sops 提供三件事:

  1. 把密文当普通文件管理 — secrets.yaml 是普通 YAML,git diff、git log、PR review 都能做。
  2. 密钥与仓库解耦 — 密文进 git,解密密钥留在机器上,多人/多机共享仓库但各用自己的私钥。
  3. 加密细节标准化 — sops edit、sops -d 封装底层算法。
加密钱包/保险箱 sops
核心问题 怎么安全存放一份秘密 怎么安全版本化/分发秘密
谁持有 一个人,一个设备 一群人/多台机器,共享仓库
关注点 加密强度 密钥管理、协作、审计、变更追踪

一句话:sops = 把 secrets 从「git 里的明文」变成「git 里的密文」,解密能力(私钥)留在机器上。

混合加密:两层结构#

sops 不是用公钥直接加密 secrets,而是混合加密:

1. 生成一把随机的对称 AES 数据密钥
2. 用 AES 密钥加密每个 value(数据层)
3. 用你的 age 公钥加密那把 AES 密钥(密钥层)

这解释了 secrets.yaml 的两层:

app_env: "SOPS_CIPHERTEXT_PLACEHOLDER_NOT_VALID" # 数据层(AES)
sops:
  age:
    - enc: "AGE_WRAPPED_DATA_KEY_PLACEHOLDER_NOT_VALID" # 密钥层(age)
      recipient: "AGE_RECIPIENT_PLACEHOLDER_NOT_VALID" # 接收者公钥

数据层字段:AES256_GCM(对称加密)、data(密文)、iv(初始化向量,保证相同明文密文不同)、tag(GCM 认证标签防篡改)、type(原数据类型)。

密钥层 + 元数据:age(用 age 公钥体系)、enc(AES 密钥用你公钥加密的结果)、recipient(你的公钥)、lastmodified、mac(整个文件完整性校验)、unencrypted_suffix、version。

加密对象与格式:sops 加密的是"值/字节流",不是"格式"#

sops 加密内容,并依据所选格式决定如何解析、保存和解密。 结构化模式需要理解 YAML、JSON、dotenv 或 INI,解密输出可能经过重新序列化,不能保证原始排版逐字节保留。binary 模式把原文件视为字节流,适合需要还原任意文件内容的场景。

两种加密粒度(关键区分):

模式 sops 怎么处理 何时用
结构化(yaml/json/dotenv/ini) 解析文件 → 分成多个键 → 每个键一个值加密 想要"一个文件多个机密、可单独引用"
binary(万能兜底) 整个文件当作一段字节流整体加密,不解析内部 任意格式文件想整体加密

binary 可用于任意字节内容:例如将 .conf 整体加密,再以 binary 输出解密,并用 cmp 检查还原。此处是验证方法,不代表当前已运行验证。所以"文件格式不支持"不是问题——用 binary 通吃;只有想要"一个文件拆多个键"时才用结构化格式。

age 和 ed25519 的关系#

它们是完全不同的密码学工具,但在同一个曲线家族。

  • ed25519:数字签名算法(Edwards25519 曲线)。SSH、Git 做身份认证。
  • age:加密工具/格式。底层用 Curve25519(X25519) 做公钥加密。

两者都源自 25519 曲线家族,但格式不同、用途不同、不能互换。ssh-to-age 能把 SSH ed25519 host key 转成 age 格式(工具按其支持的 Ed25519 密钥编码完成转换;不能把两种文件格式直接互换)——这也是 sops 能直接用 SSH host key 解密的原因。

recipient 登记制:为什么加公钥就能解密#

一对多授权模型:

sops:
  age:
    - enc: <AES 密钥用旧公钥加密>
      recipient: AGE_RECIPIENT_PLACEHOLDER_NOT_VALID
    - enc: <同一把 AES 密钥用新公钥加密>
      recipient: AGE_RECIPIENT_PLACEHOLDER_NOT_VALID
  • AES 数据密钥只有一把,但对每个 recipient 各生成一个加密副本
  • 旧机器用自己私钥解开第一个块 → 拿到 AES 密钥 → 解密数据
  • 新机器解开第二个块 → 拿到同一把 AES 密钥 → 解密数据

所以「把新机器公钥加进 recipient 就能解密」——每台机器用自己私钥解开属于自己的密钥块,不需要拷贝私钥。

为什么「clone 仓库 + 加公钥」拿不到明文#

关键安全边界:

  • 公钥只能加密,不能解密(单向门)
  • B clone 仓库后没有你的私钥 → 解不开你已加密的数据
  • B 加自己的公钥 → sops 需「已有私钥解开现有文件」才能重加密 → 没有你的私钥直接 FAILED
  • B 重新加密一份新文件 → 只是 B 自己写的内容,不含你的明文

明文泄露并不只来自私钥泄露。 编辑器临时文件、日志、备份、运行时文件、进程环境、已有授权接收者或被控制的解密机器都可能暴露秘密。密文提交也会保留键名、结构和某些元数据。

相关#


参考与延伸

  1. SOPS 官方文档 ↗
  2. SOPS CLI 源码 ↗
  3. SOPS 配置源码 ↗
  4. sops-nix 官方说明 ↗
  5. sops-nix 模块 ↗
  6. sops-nix 模板模块 ↗
  7. ssh-to-age 官方说明 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

SOPS 加密配置与密钥生命周期

  1. 01sops 原理:混合加密与接收者
  2. 02sops-nix 集成:运行时、时序与 SSH host key
  3. 03sops 使用指南:命令行操作
  4. 04sops 配置模板与运行时渲染
  5. 05sops 密钥迁移与轮换
CONTINUE READING / 下一篇sops-nix 集成:运行时、时序与 SSH host key

把加密源文件、运行时秘密文件与服务依赖连接起来。