Library / SDK
FiloSottile/age avatar
FiloSottile/age

FiloSottile/age: file encryption with small keys and UNIX pipes

A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.

23,754 stars674 forksGoBSD-3-Clause

At a glance

What is it?
age is a file encryption tool, format and Go library with explicit keys, no config file and a v1.3.0 release that adds post-quantum recipients. It fits pipelines and key-based automation, and it is the wrong tool when you need per-file ACLs or a key server.
Who is it for?
Adopt age when your encryption step should be one command inside a shell pipeline and your recipients are identified by a short public key you can paste into a config or a recipients file. Do not adopt it as a replacement for a secrets manager that enforces access control, rotation and audit, and do not adopt it expecting a key server or a config file to exist, because the README describes neither.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What age is for, and who ends up using it

The problem age addresses is narrow: you have bytes, you want them encrypted to one or more recipients, and you want that to happen inside a shell command rather than inside an application. The README frames this as UNIX-style composability, and the first example shows the shape of it: tar writes a stream, age encrypts it, the shell redirects the result to a file. Nothing in that chain needs a configuration directory or a daemon.

The audience follows from that. Build and release engineers encrypting artifacts before uploading them. Operators moving a backup off a host without installing a backup product. Go developers who want the same format available as a library, since the module path is filippo.io/age and the repository ships age.go, parse.go, primitives.go and x25519.go at the top level alongside cmd/. Anyone who has tried to explain a GPG keyring to a colleague is the target reader.

The design constraints are stated plainly in the README: small explicit keys, no config options. A public key looks like age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p. That string is short enough to paste into a chat message or a CI variable, which is the whole point. There is no trust model to configure, because there is no web of trust. You encrypt to a key you already have, or you encrypt with a passphrase.

How age encryption works: recipients, identities, one stream

The mechanism is a recipient and identity model. A recipient is a public key, either an age key generated by age-keygen or an SSH public key in ssh-ed25519 or ssh-rsa form. An identity is a secret key, stored in a file, in the AGE-SECRET-KEY-1... form or as an SSH key. Encryption takes one or more recipients; decryption takes one or more identities. The command line mirrors that asymmetry exactly, with -r and -R on the encrypt side and -i on the decrypt side.

Recipient files are the part that matters for automation. The README states that a recipient file contains one or more recipients, one per line, that empty lines and lines beginning with # are ignored as comments, and that - reads recipients from standard input. That last detail is what lets you generate a recipient list in a pipeline and encrypt to it without writing a temporary file. Identity files follow the same conventions for secret keys, and the README notes that passphrase-encrypted age files can themselves be used as identity files, which is how you keep a key encrypted at rest and still feed it to -i.

The format is specified separately at age-encryption.org/v1, and the repository carries a C2SP specification badge pointing at the same place. That separation matters more than it looks: the tool, the format and the Go library are three things, and the Rust implementation rage and the TypeScript implementation Typage exist because the format is documented independently of this codebase. If you need a second implementation to read what age wrote, the specification is where you check, not the README.

Version v1.3.0, released 2025-12-27, is titled post-quantum (and more). The repository has a pq.go file at the top level and depends on filippo.io/hpke and filippo.io/nistec in go.mod, which is consistent with a hybrid post-quantum recipient type. The README does not spell out the recipient syntax for that mode, so treat the release notes and the specification as the source for it rather than guessing a prefix.

Installing age and encrypting your first file

Installation is a package manager line on most systems. On macOS or Linux with Homebrew it is brew install age; on Debian 12 and Ubuntu 22.04 or later it is apt install age; on Fedora it is dnf install age; on Windows it is winget install --id FiloSottile.age. Debian 11 users need the bullseye-backports archive to get age v1.0.0 or newer. The README also lists pre-built binaries under dl.filippo.io for linux, darwin and other targets, with Sigsum proofs described in SIGSUM.md for verifying them. If you have a supported Go toolchain, the source install is:

bash
go install filippo.io/age/cmd/...@latest

That installs the commands in the cmd/ tree, which is where age and age-keygen live. After that, generate a key pair. The README's own example is:

bash
age-keygen -o key.txt

The tool writes the identity to key.txt and prints the public key to the terminal. You should see a line beginning with Public key: followed by a string starting with age1. Keep key.txt out of version control; the public key is the part you distribute.

Encrypting a directory tree to that recipient uses a pipe, exactly as the README demonstrates:

bash
tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age

Nothing appears on standard output except the encrypted stream, which is redirected to data.tar.gz.age. Decryption reads the identity file and writes the archive back:

bash
age --decrypt -i key.txt data.tar.gz.age > data.tar.gz

Two flags are worth knowing before you build anything on top. -a, or --armor, makes age emit PEM-encoded output, which is what you want when the ciphertext has to travel through a channel that mangles binary. -p, or --passphrase, switches from recipient-based encryption to a passphrase, which is the right choice for a one-off file you are sending to someone who has no key yet. The README notes that if the output file already exists it will be overwritten, so a script that writes into a fixed path should check or remove that path first.

Where age is the wrong tool

age has no key server, no revocation and no access control layer. If a recipient's key is compromised you cannot un-encrypt what was already written, and you cannot remove that recipient from an existing file. There is no notion of a group, a role or an expiry. For a team that needs to answer who could read a given object six months ago, age gives you the recipient list you passed on the command line and nothing more.

Key management is entirely yours. The README documents identity files and passphrase-encrypted files used as identities, but it does not document a rotation procedure, a key escrow mechanism or a recovery path. If the identity file is lost, the ciphertext is gone. That is the correct property for an encryption tool and a real operational hazard for anyone who has been trained by systems that offer account recovery.

There are also things the README simply does not cover. It does not document rollback of a version upgrade, does not describe a configuration file (there is none by design), and does not describe a Windows-native key storage integration. Hardware token support exists but lives outside this repository, in the age-plugin-yubikey project that the README links. If your requirement is that the private key never leaves a PIV token, you are adopting a plugin and its own release cycle, not just age.

Finally, age is a file and stream encryption tool, not a disk encryption tool and not a transport protocol. Encrypting a file at rest does not protect a machine that is running and unlocked, and piping through age does not authenticate the endpoints on either side of the pipe.

age vs GPG, and the other implementations

The comparison people reach for is age vs GPG, and the difference is in the key model rather than the cipher. GPG carries a web of trust, key servers, subkeys, expiry and a large option surface. age carries a public key string and a secret key file. The README's own framing is no config options, which is a description of the trade: you lose the ability to express policy and you gain the ability to read the entire command in one line. For a CI job that encrypts one artifact to one key, that trade is easy. For an organization that needs signatures, revocation lists and a documented chain of custody, GPG or a dedicated secrets platform is the more appropriate layer, and age will feel like it is missing pieces.

Among implementations of the same format, rage is a Rust implementation that the README calls interoperable, and Typage is a TypeScript implementation that the README says works in the browser, Node.js, Deno and Bun. The difference from this repository is the runtime and the packaging, not the format. A browser-side TypeScript implementation changes what is possible: you can decrypt in a web page without a native binary, which the Go command cannot do. Conversely, the Go library is what you import when your service is written in Go, via the filippo.io/age module path.

SSH keys are the other practical alternative to generating a new key pair at all. The README states that a recipient can be an SSH public key and that an identity file can be an SSH key, so a team that already distributes authorized_keys has a recipient list without introducing a new key type. The trade is that SSH keys are used for authentication elsewhere and their handling practices may be looser than you would want for long-lived encrypted archives.

Maintenance, versioning and licence

The repository is not archived, and the last push was on 2026-08-29, which is also the date of the v1.3.2 release. The preceding releases were v1.3.1 on 2025-12-28 and v1.3.0 on 2025-12-27. That cadence suggests a project that ships when there is something to ship rather than on a schedule, and the gap between v1.3.0 and v1.3.1 is one day, which is consistent with a follow-up fix rather than a feature release.

Upgrade cost is low on the surface because there is no configuration to migrate. The real cost is in the toolchain: go.mod declares go 1.25.0 and a toolchain of go1.27.0 for release builds, so building from source requires a recent Go. If you consume age as a library rather than a binary, your dependency graph picks up filippo.io/edwards25519, filippo.io/hpke, filippo.io/nistec, golang.org/x/crypto, golang.org/x/sys and golang.org/x/term. The post-quantum additions in v1.3.0 are the reason filippo.io/hpke and filippo.io/nistec are there, and they are the part of the dependency set most likely to move as the format evolves.

The licence is BSD-3-Clause, stated in the repository and in the LICENSE file at the top level. That is a permissive licence, which generally means you can use the code in closed products provided you keep the copyright notice and the disclaimer, but the exact obligations depend on how you redistribute it and this is not legal advice. If you vendor the code or ship a binary, read LICENSE and your own counsel's guidance rather than treating the identifier as sufficient.

Editorial conclusion

Adopt age when your encryption step should be one command inside a shell pipeline and your recipients are identified by a short public key you can paste into a config or a recipients file. Do not adopt it as a replacement for a secrets manager that enforces access control, rotation and audit, and do not adopt it expecting a key server or a config file to exist, because the README describes neither. Before rolling it out, verify three things on your own machines: that the package your distribution ships is at least v1.3.0 if you need the post-quantum recipients introduced in that release, that your recipients file parses with comments and blank lines as documented, and that your decryption path has the identity file available, since age does not recover a lost identity for you.

Frequently asked questions

How does age encryption work?

You encrypt to one or more recipients, which are age public keys beginning with age1 or SSH public keys, and you decrypt with one or more identity files containing the matching secret keys. The command line reflects this with -r and -R for recipients and -i for identities, and the format itself is specified separately at age-encryption.org/v1.

What is age-keygen used for?

age-keygen generates a key pair. The README example runs age-keygen -o key.txt, which writes the identity to key.txt and prints the public key, a string starting with age1, for you to distribute to anyone who wants to encrypt to you.

What does AgeKey or an age key mean?

In age there are two key forms: a recipient, which is the public key printed by age-keygen and begins with age1, and an identity, which is the secret key stored in an identity file and written as AGE-SECRET-KEY-1. Encryption takes recipients, decryption takes identities.

Official sources

  1. FiloSottile/age on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/filosottile-age.svg)](https://hysenlabs.com/projects/filosottile-age)
Community notes

Community notes