OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 098

sops principles: hybrid encryption and recipients

Understand the boundaries of encrypted version control, data keys, and authorization for multiple recipients.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSSOPS encrypted configuration and key lifecycles ↗
About 5 min readStart reading

What problem does sops solve?#

sops is not an “encrypted wallet/vault,” but a “git-friendly approach to version-controlling secrets.” The core problem: secret configuration that needs versioning or review can be stored in git as ciphertext, while plaintext and decryption keys should stay outside the repository.

To meet that goal, sops provides three things:

  1. Manage ciphertext as ordinary files — secrets.yaml is ordinary YAML, so git diff, git log, and PR reviews all remain available.
  2. Decouple keys from the repository — ciphertext goes into git, while decryption keys stay on machines. Multiple people/machines share the repository but each uses their own private key.
  3. Standardize the encryption details — sops edit and sops -d wrap the underlying algorithms.
Encrypted wallet/vault sops
Core problem How to store a secret securely How to version/distribute secrets securely
Who holds it One person, one device A group of people/multiple machines sharing a repository
Focus Encryption strength Key management, collaboration, auditing, change tracking

In one sentence: sops = turning secrets from “plaintext in git” into “ciphertext in git,” while keeping decryption capability (private keys) on the machines.

Hybrid encryption: a two-layer structure#

sops does not encrypt secrets directly with a public key; it uses hybrid encryption:

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

This explains the two layers in 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" # 接收者公钥

Data-layer fields: AES256_GCM (symmetric encryption), data (ciphertext), iv (initialization vector, ensuring identical plaintext produces different ciphertext), tag (GCM authentication tag to prevent tampering), and type (the original data type).

Key layer + metadata: age (using the age public-key system), enc (the AES key encrypted with your public key), recipient (your public key), lastmodified, mac (whole-file integrity check), unencrypted_suffix, and version.

Encryption targets and formats: sops encrypts “values/byte streams,” not “formats”#

sops encrypts content, using the selected format to determine how to parse, store, and decrypt it. Structured modes need to understand YAML, JSON, dotenv, or INI. Decrypted output may be reserialized, so the original formatting is not guaranteed to survive byte for byte. Binary mode treats the original file as a byte stream and is suitable when arbitrary file contents must be restored.

Two levels of encryption granularity (an important distinction):

Mode How sops handles it When to use it
Structured (yaml/json/dotenv/ini) Parses the file → separates it into multiple keys → encrypts the value for each key When you want “multiple secrets in one file, individually referenceable”
binary (the general-purpose fallback) Encrypts the entire file as one byte stream, without parsing its contents When you want to encrypt an entire file in any format

binary works with arbitrary byte content: for example, encrypt a whole .conf file, decrypt it with binary output, and check the restored file with cmp. This describes a validation method, not a claim that the validation has already been run. Thus, “unsupported file format” is not a problem: binary handles any format. Use a structured format only when you want to “split a file into multiple keys.”

The relationship between age and ed25519#

They are entirely different cryptographic tools, but belong to the same curve family.

  • ed25519: a digital signature algorithm (the Edwards25519 curve), used for authentication in SSH and Git.
  • age: an encryption tool/format. It uses Curve25519 (X25519) for public-key encryption underneath.

Both derive from the 25519 curve family, but their formats and purposes differ, and they are not interchangeable. ssh-to-age can convert an SSH ed25519 host key into age format (the tool converts the Ed25519 key encodings it supports; the two file formats cannot be swapped directly). This is also why sops can decrypt using an SSH host key directly.

Registering recipients: why adding a public key enables decryption#

A one-to-many authorization model:

sops:
  age:
    - enc: <AES 密钥用旧公钥加密>
      recipient: AGE_RECIPIENT_PLACEHOLDER_NOT_VALID
    - enc: <同一把 AES 密钥用新公钥加密>
      recipient: AGE_RECIPIENT_PLACEHOLDER_NOT_VALID
  • There is only one AES data key, but a separate encrypted copy is generated for each recipient
  • The existing machine uses its private key to unlock the first block → obtains the AES key → decrypts the data
  • The new machine unlocks the second block → obtains the same AES key → decrypts the data

This is why “adding a new machine’s public key as a recipient enables decryption”: each machine uses its own private key to unlock its own key block, with no need to copy private keys.

Why “cloning the repository + adding a public key” does not reveal plaintext#

The key security boundaries:

  • A public key can encrypt but cannot decrypt (a one-way door)
  • B clones the repository but does not have your private key → cannot decrypt your encrypted data
  • B adds their own public key → sops needs “an existing private key to decrypt the current file” before re-encrypting → without your private key, it immediately FAILS
  • B encrypts a new file → it contains only what B wrote, not your plaintext

Private-key compromise is not the only way plaintext can leak. Editor temporary files, logs, backups, runtime files, process environments, already authorized recipients, or a compromised decryption machine can all expose secrets. Ciphertext commits also retain key names, structure, and some metadata.

Related reading#


References & further reading

  1. Official SOPS Documentation ↗
  2. SOPS CLI Source ↗
  3. SOPS Configuration Source ↗
  4. Official sops-nix Documentation ↗
  5. sops-nix Module ↗
  6. sops-nix Templates Module ↗
  7. Official ssh-to-age Documentation ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

SOPS encrypted configuration and key lifecycles

  1. 01sops principles: hybrid encryption and recipients
  2. 02sops-nix integration: runtime, ordering, and SSH host keys
  3. 03sops usage guide: command-line operations
  4. 04sops configuration templates and runtime rendering
  5. 05sops key migration and rotation
CONTINUE READING / NEXT ARTICLEsops-nix integration: runtime, ordering, and SSH host keys

Connect encrypted source files, runtime secret files, and service dependencies.