CLI tool
str4d/rage avatar
str4d/rage

rage: the Rust implementation of age, and when to use it instead of the Go binary

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

3,673 stars160 forksRustApache-2.0

At a glance

What is it?
rage is a command-line file encryption tool that implements the age format, written in Rust and installable via cargo, brew or your distribution's package manager. It keeps keys small and explicit, ships no config file, and composes like any other UNIX filter.
Who is it for?
Adopt rage if you want age-format encryption from a Rust toolchain, need the library as a crate, or want SSH keys and YubiKey PIV tokens to work as identities without a keyring daemon. Do not adopt it if you need ssh-agent support, want a graphical key manager, or expect a config file to hold your defaults: rage has none, and every recipient, identity and flag must be passed on the command line.
Can I use it commercially?
Yes. Apache-2.0 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 42 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

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

Editorial analysis

What rage solves, and who it is for

The age format was designed as a replacement for PGP-style file encryption, and rage is its Rust implementation. The pitch is narrow on purpose: small explicit keys, no config options, UNIX-style composability. If you have ever tried to explain to a colleague which GPG key to use, which keyring it lives in, and why the recipient string has three colons in it, that is the problem rage removes. A public key is a single line beginning with age1. There is no key server, no web of trust, no trust model to configure. You hand the tool a recipient, it hands you ciphertext.

The intended audience is engineers who encrypt files as part of a script or a build step. The README's own examples pipe a PNG through rage and redirect the result to a .age file. That is the whole interaction model. Recipients can be age public keys or SSH public keys, so a team that already distributes ~/.ssh/id_ed25519.pub files can start encrypting to those keys without a separate key distribution ceremony. Hardware PIV tokens such as YubiKeys are supported, but through a separate plugin, age-plugin-yubikey, not in the core binary.

What rage is not: a secrets manager, a key server, or a tool with a persistent configuration. Every invocation states its recipients and identities. That is a deliberate trade-off, and it is the source of both the clarity and most of the friction.

How the age format and rage's crate layout fit together

The repository is a Cargo workspace with four members: age, age-core, age-plugin and rage. The last one is the CLI binary. The first three are libraries, and the workspace Cargo.toml pins their versions together (age at 0.12.1, age-core at 0.12.0), which means a downstream crate depending on age gets the same primitives the CLI uses. That is the main structural difference from the reference Go implementation at filippo.io/age: rage exposes the format as a Rust library you can embed, not only as a binary.

The workspace dependencies list reads like a map of the specification. ChaCha20-Poly1305 from RFC 7539 provides the authenticated encryption. X25519 from RFC 7748 handles the key agreement. scrypt from RFC 7914 backs passphrase encryption. HKDF and HMAC with SHA-256 derive keys. Bech32 encodes the keys, which is why they look the way they do. Newer entries in that list, ml-kem from FIPS 203 and HPKE from RFC 9180, sit alongside the classical primitives. The README does not describe a post-quantum mode in the usage text, so treat those as dependency-level facts rather than a documented CLI feature.

Secret handling is explicit in the dependency list too: secrecy, subtle and zeroize. That is a signal about intent, not a guarantee about every code path. The practical consequence for a user is that identities are read from files or standard input and are not cached between runs. There is no agent process holding your key in memory, which also means there is no agent to attack.

Installing rage and encrypting a first file

The README lists a package per environment. On macOS or Linux with Homebrew the command is brew install rage. On Arch Linux the package is rage-encryption, and the same fallback name appears for openSUSE Tumbleweed, FreeBSD and the Debian packages linked from the releases page. If you have a Rust toolchain at 1.85 or newer, cargo install rage builds it from source. The README asks packagers to use the name rage and reserve rage-encryption for cases where a global namespace forces a conflict, and it explicitly says not to rename the binaries.

The README's usage section shows how recipients and identities are supplied. Encryption to a recipient looks like this:

bash
$ rage -o example.png.age -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u \
    -r age1ex4ty8ppg02555at009uwu5vlk5686k3f23e7mac9z093uvzfp8sxr5jum example.png

The -o flag names the output file and -r names a recipient, repeated once per recipient. With no -o, rage writes to standard output, which is what makes it pipeable. Multiple recipients can also be listed one per line in a file passed with -R. The README's example reads such a file and writes to standard output:

bash
$ rage -R recipients.txt example.jpg > example.jpg.age

The README notes that lines beginning with # and empty lines in that file are ignored, so you can annotate a recipients file with names. Passing - as the argument to -R or -i reads that list from standard input. Identity files are supplied with -i, and the README shows decrypting with an SSH private key:

bash
$ rage -d -i ~/.ssh/id_ed25519 example.png.age > example.png

Passphrases, SSH keys and what the CLI will not do for you

Passphrase mode is where rage makes a choice that some users will find surprising. Running rage -p and leaving the passphrase prompt empty causes rage to generate a passphrase for you and print it in a hyphenated word list, for example kiwi-general-undo-bubble-dwarf-dizzy-fame-side-sunset-sibling, which the README shows in its passphrase example. That passphrase is printed once, to the terminal. If you lose it, the file is gone. There is no recovery path, and the README does not document one, because the format has no escrow mechanism.

If a binary named pinentry is on your PATH, rage uses it to collect the passphrase instead of the terminal. The PINENTRY_PROGRAM environment variable overrides the binary name or path, and setting it to the empty string forces the fallback to the CLI prompt. That is the one environment variable the README documents, and it is worth knowing about in headless environments where a pinentry binary exists but cannot display anything.

SSH support is a convenience feature with a stated boundary. rage accepts ssh-rsa and ssh-ed25519 public keys as recipients and the corresponding private key files as identities. The README says plainly that ssh-agent is not supported. For anyone used to unlocking a hardware-backed SSH key once per session, this is the sharpest limitation in the tool: the private key file has to be readable by the process running rage. Passphrase-protected age identity files are supported as identities, and rage will decrypt them on the fly, which the README suggests is useful when the identity file is stored remotely. That is a partial workaround, not an agent.

Where rage is the wrong tool

The absence of configuration is a feature until it is not. If your workflow needs per-project defaults, a recipient list that changes with the environment, or a policy layer that refuses certain key types, rage gives you nothing to hang that on. You will end up wrapping it in a shell script, and at that point the wrapper is the thing that needs reviewing, not the encryption tool.

There is also no key management. rage-keygen produces keys; it does not rotate them, revoke them, or tell you which key encrypted a given file. A file encrypted to three recipients carries no readable list of who those recipients were. If you need to answer the question of who can decrypt an artifact six months later, you have to keep that mapping yourself. For a single developer encrypting a backup, that is fine. For a team with staff turnover, it is a real operational gap.

The third case is the one the README states outright: ssh-agent is not supported. If your security posture depends on private keys never leaving a hardware token or an agent's memory, rage's SSH path does not meet it. The YubiKey route through age-plugin-yubikey is the intended answer there, and it is a separate installation with its own plugin protocol, not a flag you turn on. Finally, if you need a format that other tools already read without an age implementation, rage's output will not help you; age is a format, and interoperability depends on the other side supporting it.

rage against the Go implementation and against GPG

The README names filippo.io/age as the reference interoperable Go implementation. The two produce compatible output, since both implement the same specification at age-encryption.org/v1, so the choice is not about which one can read the other's files. It is about what surrounds the binary. The Go implementation is a single static binary with no runtime dependency, which matters on minimal container images. rage is a Rust workspace that also publishes the age, age-core and age-plugin crates, so if you are writing Rust and want to encrypt in-process rather than shelling out, rage's library side is the reason to pick it. If you are not writing Rust, that advantage disappears and you are choosing between two CLIs that do the same job.

Against GPG the difference is philosophical rather than cryptographic. GPG brings a keyring, a trust model, key servers and a configuration directory. age and rage bring a key file and a recipient string. GPG can sign, certify and handle X.509; rage encrypts and decrypts. If you need signatures or an existing PKI, rage is the wrong shape. If you only ever wanted to encrypt a file to a colleague's key without explaining the trust model, that is exactly the case age was designed for, and rage is one of its implementations.

Comparisons with tools like sops or transcrypt come up in search results, but they solve adjacent problems: sops manages secrets inside structured config files, transcrypt manages repository-level file encryption through git filters. rage is a general-purpose file encryption CLI. It can be a component of those workflows, not a replacement for them.

Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-08-20. The most recent release in the list is v0.12.1 from 2026-07-14, with v0.11.4 published the same day as a maintenance line for the previous minor version. That pattern matters for upgrade planning: there is a maintained 0.11 branch alongside 0.12, so a team pinned to 0.11 is not immediately stranded, but the workspace Cargo.toml points age at 0.12.1, and the two lines will diverge.

Licensing is dual: the workspace declares MIT OR Apache-2.0, and the repository carries both LICENSE-MIT and LICENSE-APACHE at the top level. That is the standard permissive Rust pairing. It means you can embed the age crate in a proprietary product without a copyleft obligation, but you should confirm which of the two you are relying on and keep the relevant notice, since the choice is yours to make and the files are separate. None of this is legal advice; if the distinction matters to your organisation, read both files.

The upgrade surface is smaller than for most CLIs because there is no config file to migrate. A version bump changes the binary, not a settings directory. The things to re-check on upgrade are the flags you depend on and the plugin protocol if you use age-plugin-yubikey, since a plugin is a separate binary with its own release cycle. The rust-toolchain.toml in the repository pins the toolchain used to build it, which tells you the minimum you need if you build from source rather than installing a package.

Editorial conclusion

Adopt rage if you want age-format encryption from a Rust toolchain, need the library as a crate, or want SSH keys and YubiKey PIV tokens to work as identities without a keyring daemon. Do not adopt it if you need ssh-agent support, want a graphical key manager, or expect a config file to hold your defaults: rage has none, and every recipient, identity and flag must be passed on the command line. Before rolling it into a pipeline, verify three things: that your platform has a package or pre-built binary, that the recipients file you generate is readable by the process that will run the encryption, and that your recovery plan covers the identity file, because losing it means losing the data.

Frequently asked questions

What is str4d/rage, and how does it relate to age?

rage is a Rust implementation of the age file encryption format, published by str4d. It provides both a CLI and the age, age-core and age-plugin libraries, and it interoperates with the reference Go implementation at filippo.io/age because both follow the specification at age-encryption.org/v1.

How do I install rage on Linux or macOS?

The README lists brew install rage for Homebrew on macOS or Linux, apk add rage on Alpine edge, pacman -S rage-encryption on Arch, zypper install rage-encryption on openSUSE Tumbleweed, and cargo install rage for a Rust toolchain at 1.85 or newer. Pre-built binaries are also available from the releases page for Windows, Linux and macOS.

Can rage decrypt files encrypted by the Go age tool?

Yes. The README describes filippo.io/age as the reference interoperable Go implementation, and both tools implement the same format specification at age-encryption.org/v1. Interoperability follows from the shared format rather than from any conversion step.

Does rage support ssh-agent?

No. The README states directly that ssh-agent is not supported, although rage can encrypt to ssh-rsa and ssh-ed25519 public keys and decrypt with the corresponding private key files. For hardware-backed keys, the README points to the age-plugin-yubikey plugin instead.

What happens if I lose the passphrase for a file encrypted with rage -p?

There is no recovery path. The README shows rage generating a passphrase such as kiwi-general-undo-bubble-dwarf-dizzy-fame-side-sunset-sibling and printing it once, and the format has no escrow or reset mechanism. Losing the passphrase means losing the file.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. str4d/rage on GitHub
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/str4d-rage.svg)](https://hysenlabs.com/projects/str4d-rage)