{"id":51151337,"url":"https://github.com/dcadolph/cipher","last_synced_at":"2026-06-26T06:04:49.790Z","repository":{"id":364671733,"uuid":"586430908","full_name":"dcadolph/cipher","owner":"dcadolph","description":"Programmatic SOPS for Go: encrypt, decrypt, rotate, walk, edit, and audit secret files. CLI + library.","archived":false,"fork":false,"pushed_at":"2026-06-21T15:36:31.000Z","size":1578,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-21T17:15:20.014Z","etag":null,"topics":["age","aws-kms","azure-keyvault","cli","configuration-management","devops","encryption","gcp-kms","golang","gpg","kms","mozilla-sops","pgp","pre-commit","secrets","secrets-management","security","sops","vault","yaml"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dcadolph.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-08T05:15:17.000Z","updated_at":"2026-06-21T15:28:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dcadolph/cipher","commit_stats":null,"previous_names":["dcadolph/cipher"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dcadolph/cipher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcadolph%2Fcipher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcadolph%2Fcipher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcadolph%2Fcipher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcadolph%2Fcipher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcadolph","download_url":"https://codeload.github.com/dcadolph/cipher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcadolph%2Fcipher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34805127,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["age","aws-kms","azure-keyvault","cli","configuration-management","devops","encryption","gcp-kms","golang","gpg","kms","mozilla-sops","pgp","pre-commit","secrets","secrets-management","security","sops","vault","yaml"],"created_at":"2026-06-26T06:04:49.125Z","updated_at":"2026-06-26T06:04:49.775Z","avatar_url":"https://github.com/dcadolph.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![cipher](assets/cipher-banner.png)\n\n# cipher\n\n[![test](https://github.com/dcadolph/cipher/actions/workflows/test.yml/badge.svg)](https://github.com/dcadolph/cipher/actions/workflows/test.yml)\n[![lint](https://github.com/dcadolph/cipher/actions/workflows/lint.yml/badge.svg)](https://github.com/dcadolph/cipher/actions/workflows/lint.yml)\n[![codecov](https://codecov.io/gh/dcadolph/cipher/graph/badge.svg)](https://codecov.io/gh/dcadolph/cipher)\n[![Go Reference](https://pkg.go.dev/badge/github.com/dcadolph/cipher.svg)](https://pkg.go.dev/github.com/dcadolph/cipher)\n[![Go Report Card](https://goreportcard.com/badge/github.com/dcadolph/cipher)](https://goreportcard.com/report/github.com/dcadolph/cipher)\n[![License](https://img.shields.io/github/license/dcadolph/cipher.svg)](LICENSE)\n\nProgrammatic [SOPS](https://github.com/getsops/sops), from Go. One library and one CLI for encrypt, decrypt, rotate, walk, edit, and audit. Drop in next to your existing sops files and keep going.\n\n![cipher demo](assets/demo.gif)\n\nEvery release is exercised end to end against real Vault Transit, AWS KMS through LocalStack, and a fresh PGP keyring. The on disk format is the standard sops format, so the upstream sops binary reads what cipher writes.\n\n## What you can do\n\n- Encrypt and decrypt YAML, JSON, ENV, INI, or binary files with age, AWS KMS, GCP KMS, Vault Transit, Azure Key Vault, or PGP.\n- Edit encrypted files in `$EDITOR`, re-encrypted on save with the original recipients.\n- Rotate the per-file encryption key on demand or on age (`--older-than 90d`).\n- Add or drop recipients without re-encrypting the payload.\n- Walk a directory tree in parallel and apply any of the above to every matching file.\n- Route per-path recipient selection from a [`.sops.yaml`](https://github.com/getsops/sops) policy file.\n- Block plaintext commits with a git pre-commit hook.\n- Stream secrets through Go [`net/http`](https://pkg.go.dev/net/http) middleware and emit [OpenTelemetry](https://opentelemetry.io) traces.\n\n## When to pick cipher\n\n| Tool | Best for | Tradeoff |\n|------|----------|----------|\n| **cipher** | Secrets committed to git plus Go integration, parallel directory walks, audit and drift checks, and a pre-commit hook. | Pre-1.0. Go API may break between minor versions. |\n| raw `sops` CLI | Secrets in git when one file at a time is enough and no Go consumer needs an encrypt API. | No directory walker. The sops Go API only decrypts. |\n| HashiCorp Vault | Runtime secrets your app fetches over the network on each request. | Server to run and maintain. |\n| AWS Secrets Manager | AWS native runtime secrets resolved by IAM. | AWS lock in. Runtime only. |\n| Azure Key Vault | Azure native runtime secrets. | Azure lock in. Runtime only. |\n\nPick cipher when the secrets live in your repo and you want one tool that behaves the same way from Go code, from CI, and from your editor. Pick Vault or one of the managed runtime stores when the secrets live in a server and the app fetches them at runtime.\n\n## Quickstart\n\n### CLI\n\n```sh\nbrew install dcadolph/tap/cipher\n```\n\n```sh\nage-keygen -o key.txt\nexport SOPS_AGE_KEY_FILE=$PWD/key.txt\nPUB=$(age-keygen -y key.txt)\n\necho \"db_password: super-secret\" \u003e prod.yaml\ncipher encrypt --age \"$PUB\" -i prod.yaml\ncipher decrypt prod.yaml\n```\n\nSee [`cmd/README.md`](cmd/README.md) for every verb and flag.\n\n### Library\n\n```go\nimport (\n    \"github.com/dcadolph/cipher\"\n    \"github.com/dcadolph/cipher/age\"\n)\n\nid, _ := age.GenerateIdentity()\nkp, _ := age.NewProvider(id.Recipient)\n\nenc := cipher.NewEncoder(kp)\nciphertext, _ := enc.Encode(ctx, \"secrets.yaml\", []byte(\"foo: bar\\n\"))\n\ndec := cipher.NewDecoder()\nplain, _ := dec.Decode(ctx, \"secrets.yaml\", ciphertext)\n```\n\nFull API reference at [godoc](https://pkg.go.dev/github.com/dcadolph/cipher). Runnable per-backend programs live under [`examples/`](examples/).\n\n## Recipes\n\n### Encrypt every plaintext file that matches `.sops.yaml`\n\n```sh\ncipher fix ./secrets\n```\n\nWalks the tree, finds plaintext files that match a creation rule, and encrypts them in place with the recipients the rule names.\n\n### Edit a secret in your text editor\n\n```sh\ncipher edit secrets.yaml\n```\n\nDecrypts to a `0600` file in a fresh `0700` temp directory, opens `$EDITOR`, re-encrypts on save with the original recipients. Plaintext never lands on a shared path.\n\n### Add a teammate to an existing file\n\n```sh\ncipher add-recipient secrets.yaml --age age1bob... -i\n```\n\nThe wrapped data key picks up Bob. The encrypted payload itself does not change.\n\n### Rotate the data key on every file older than 90 days\n\n```sh\ncipher walk rotate ./secrets --config .sops.yaml --older-than 90d --parallel 8\n```\n\nSame recipient set, fresh AES key, payload re-encrypted under it. Files newer than the cutoff are skipped.\n\n### Audit who can decrypt what\n\n```sh\ncipher recipients list secrets.yaml --pretty\ncipher recipients drift ./secrets --config .sops.yaml\n```\n\n`list` prints the recipients recorded in a file. `drift` reports files whose recipient set no longer matches `.sops.yaml`.\n\n### Block plaintext commits\n\n```sh\ncat \u003e .git/hooks/pre-commit \u003c\u003c'EOF'\n#!/usr/bin/env bash\nexec cipher precommit\nEOF\nchmod +x .git/hooks/pre-commit\n```\n\nRefuses any staged file that matches a `.sops.yaml` rule but is still plaintext.\n\n## Architecture\n\n```\nYour Go code or CLI\n        |\n        v\ncipher.Encoder / cipher.Decoder  -----\u003e  getsops/sops Go API\n        |\n        v\ncipher.KeyProvider\n        |\n        +--\u003e age\n        +--\u003e AWS KMS\n        +--\u003e GCP KMS\n        +--\u003e Vault Transit\n        +--\u003e Azure Key Vault\n        +--\u003e PGP\n```\n\ncipher is a thin Go layer over the [SOPS](https://github.com/getsops/sops) Go API. Each backend implements `cipher.KeyProvider` and reads credentials the same way the `sops` binary does. The on disk format is the SOPS format, so anything that decrypts SOPS files (including the upstream `sops` binary) reads what cipher writes.\n\n## Concepts\n\nEach SOPS-encrypted file holds a per-file AES-256 *data key* that protects the secret payload. That data key is itself wrapped (re-encrypted) once for each *recipient* you grant access to. A recipient is whatever the backend understands as an identity:\n\n| Backend | A recipient looks like |\n|---------|------------------------|\n| age | `age1...` public key |\n| AWS KMS | `arn:aws:kms:...` key ARN |\n| GCP KMS | `projects/.../cryptoKeys/...` resource ID |\n| Vault Transit | `https://vault/.../keys/\u003cname\u003e` URI |\n| Azure Key Vault | `https://\u003cvault\u003e.vault.azure.net/keys/\u003ckey\u003e/\u003cver\u003e` URL |\n| PGP | A GPG key fingerprint |\n\nAnyone holding the matching private key (or IAM access) can unwrap their copy of the data key and decrypt the file.\n\n## Backends\n\n| Backend | Package |\n|---------|---------|\n| age | `cipher/age` |\n| AWS KMS | `cipher/kms` |\n| GCP KMS | `cipher/gcpkms` |\n| Vault Transit | `cipher/vault` |\n| Azure Key Vault | `cipher/azkv` |\n| PGP | `cipher/pgp` |\n\nEach implements `cipher.KeyProvider`. Mix them with `cipher.MergeProviders`. Use `cipher.NewShamirRule` for threshold-of-N across backends.\n\n## Install\n\n### CLI\n\n```sh\n# Homebrew (macOS, Linux)\nbrew install dcadolph/tap/cipher\n\n# Go toolchain\ngo install github.com/dcadolph/cipher/cmd/cipher@latest\n\n# From source\ngit clone https://github.com/dcadolph/cipher \u0026\u0026 cd cipher \u0026\u0026 make install\n```\n\nPrebuilt binaries and checksums for Linux, macOS, and Windows ship with every [release](https://github.com/dcadolph/cipher/releases).\n\n### Library\n\n```sh\ngo get github.com/dcadolph/cipher\n```\n\nRequires Go 1.25 or newer.\n\n## Decrypt credentials\n\nEach backend reads decryption credentials the same way the [SOPS](https://github.com/getsops/sops) binary does:\n\n| Backend | Decrypt credential |\n|---------|---------------------|\n| age | `SOPS_AGE_KEY_FILE` pointing at the secret key file, or `SOPS_AGE_KEY` in the environment. |\n| AWS KMS | AWS credentials via the default chain. |\n| GCP KMS | Application-default credentials. |\n| Vault Transit | `VAULT_TOKEN` and `VAULT_ADDR`. |\n| Azure Key Vault | Azure default credential chain. |\n| PGP | The `gpg` binary on `PATH` with the matching private key in the keyring. |\n\n## FAQ\n\n**Which backend should I pick?**\n\n- *age*: solo dev or small team, minimum infra, secrets in git. Recommended starting point.\n- *AWS KMS / GCP KMS / Azure Key Vault*: cloud workloads with IAM-driven access and a managed audit trail.\n- *Vault Transit*: self-hosted HashiCorp Vault already in your stack.\n- *PGP*: existing GPG keyring or regulatory requirement.\n\n**Why not just use the `sops` CLI directly?**\n\nYou can. cipher decrypt is wire-compatible. cipher adds a Go library for *encryption* (sops ships decrypt only), a parallel directory walker, atomic writes, recipient diff and audit tools, a built-in git pre-commit hook, and an `$EDITOR` workflow that re-uses the file's existing recipients.\n\n**Is my data key safe across rotation?**\n\nYes. Rotation generates a fresh AES-256 key, re-encrypts the payload under it, then wraps the new key for the same recipient set. The old data key is never persisted after the rotation completes.\n\n**What about Shamir threshold-of-N?**\n\nSupported via `cipher.NewShamirRule` (library) or `--shamir-threshold N` (CLI). Split recipients into K key groups. Any N groups can recover the data key.\n\n**Can I use cipher with a non-Go service?**\n\nDecrypt yes, with the standard `sops` binary. Encrypt also yes. The on-disk format is identical to what `sops` produces, so any consumer that decrypts SOPS files reads what cipher writes.\n\n**Is the API stable?**\n\nPre-1.0. The on-disk format is the SOPS format and stays compatible. The Go API may break between minor versions until 1.0. Lock to an exact module version if you ship a binary.\n\n## Roadmap\n\ncipher is pre-1.0. The on disk format is the sops format and stays compatible across releases. The Go API may break between minor versions until 1.0 lands.\n\nOpen work for the road to 1.0:\n\n- Real round trip integration coverage for GCP KMS and Azure Key Vault. Both backends currently rely on shape and identity format tests because no usable open source emulator exists for either.\n- Stabilize the EncoderOptions surface so a 1.0 tag freezes the public API.\n- Land cipher in homebrew core so brew install cipher works without the tap.\n\nOpen an issue or a PR if you want to push any of these forward.\n\n## Docs\n\n| Surface | Covers |\n|---------|--------|\n| [examples/](examples/) | Runnable Go programs for every backend and most cross-cutting features. |\n| [cmd/README.md](cmd/README.md) | Every CLI verb, every flag, runnable examples. |\n| [godoc](https://pkg.go.dev/github.com/dcadolph/cipher) | Full library API reference. |\n| [SECURITY.md](SECURITY.md) | Threat model, key handling, disclosure path. |\n| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, what CI runs, commit and PR style. |\n| [RELEASING.md](RELEASING.md) | Release workflow, Homebrew tap setup, what every tag publishes. |\n| `cipher demo` | Six in-browser cinematic explainers, about five minutes total. |\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcadolph%2Fcipher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcadolph%2Fcipher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcadolph%2Fcipher/lists"}