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:
- Manage ciphertext as ordinary files —
secrets.yamlis ordinary YAML, sogit diff,git log, and PR reviews all remain available. - 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.
- Standardize the encryption details —
sops editandsops -dwrap 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#
- Runtime use → sops-nix Integration
- Command-line operations → A Guide to Using sops
- References: sops Manual | age Format Discussion
References & further reading
- Official SOPS Documentation ↗
- SOPS CLI Source ↗
- SOPS Configuration Source ↗
- Official sops-nix Documentation ↗
- sops-nix Module ↗
- sops-nix Templates Module ↗
- Official ssh-to-age Documentation ↗
Adapted from technical notes. Examples use generic names.