Releases: FiloSottile/age
age v1.2.1: security fix
This release fixes a security vulnerability that could allow an attacker to execute an arbitrary binary under certain conditions.
See GHSA-32gq-x56h-299c.
Plugin names may now only contain alphanumeric characters or the four special characters +-._
.
Thanks to ⬡-49016 for reporting this issue.
age v1.2.0
A small release to build the release binaries with a more recent Go toolchain, and to fix a couple CLI edge cases (#491, #555).
The Go module now exposes a plugin package that provides an age plugin client. That is, Recipient and Identity implementations that invoke a plugin binary, allowing the use of age plugins in Go programs.
Finally, Recipients can now return a set of "labels" by implementing RecipientWithLabels. This allows replicating the special behavior of the scrypt Recipient in third-party Recipients, or applying policy useful for authenticated or post-quantum Recipients.
// RecipientWithLabels can be optionally implemented by a Recipient, in which
// case Encrypt will use WrapWithLabels instead of Wrap.
//
// Encrypt will succeed only if the labels returned by all the recipients
// (assuming the empty set for those that don't implement RecipientWithLabels)
// are the same.
//
// This can be used to ensure a recipient is only used with other recipients
// with equivalent properties (for example by setting a "postquantum" label) or
// to ensure a recipient is always used alone (by returning a random label, for
// example to preserve its authentication properties).
type RecipientWithLabels interface {
WrapWithLabels(fileKey []byte) (s []*Stanza, labels []string, err error)
}
age v1.1.1
age v1.1.1 is a patch release to fix go install filippo.io/age/...@latest
.
age v1.1.0: plugin and YubiKeys support
age is a simple, modern and secure file encryption tool, format, and Go library. It features small explicit keys, no config options, and UNIX-style composability. Learn more by reading the README, the age(1) man page, the Go API reference, the format specification, or the full release changelog. Watch the repository or follow @[email protected] to be notified of new releases.
🛠️ FYI, age now has an extensive test suite which all age implementations are encouraged to adopt.
Plugin support
The age CLI now supports plugins, such as age-plugin-yubikey by @str4d. To try it on macOS with Homebrew:
$ brew upgrade age
$ brew install age-plugin-yubikey
$ age-plugin-yubikey # interactive setup
$ age -r age1yubikey1qwt50d05nh5vutpdzmlg5wn80xq5negm4uj9ghv0snvdd3yysf5yw3rhl3t
$ age -d -i age-yubikey-identity-388178f3.txt
Plugins must be loaded explicitly by using their respective recipient or identity, and are not tied to a specific header stanza type. This means plugins can be used not only to support new recipient types such as PIV tokens (i.e. YubiKeys) or cloud KMS solutions, but also to produce passphrase-encrypted files that can be decrypted without plugins, to store age native private keys on secure elements, or even for agent functionality or to proxy decryption operations to remote machines.
Plugins operate over a simple textual stdin/stdout protocol (C2SP/C2SP#5). Developers are encouraged to reach out with plugin ideas and announcements. Read more in the relevant man page section.
Breaking changes
If -i
is used, passphrase-encrypted files are now rejected. Previously, a passphrase-encrypted file was auto-detected and the identity file was ignored. This could lead to unexpected behavior, such as a script blocking for user interaction, based on potentially untrusted input files. Now, age -d
must be invoked without -i
arguments to decrypt passphrase-encrypted files. A helpful error is printed otherwise. This should not break any automated system as passphrase decryption was always interactive.
Empty final chunks are now rejected. If a payload was a multiple of 64KiB long, there were two valid encryptions for it: with a "full" last chunk encrypting 64KiB, or with an additional "empty" chunk encrypting 0 bytes. age, rage, and all other known implementations only ever produced the former. (Note that age will forever decrypt files it generated.) The latter is now rejected. The specification has been updated (C2SP/C2SP#13) and test cases are included in the test suite.
Minor changes
PKCS#8-encoded Ed25519 private keys (such as 1Password exports) are now supported as SSH identities.
If an armored file is pasted into the terminal, age will now attempt to wait until the end of the file before prompting for a password.
Some invalid files are now correctly rejected, in particular encrypted files with trailing data. (Yay for the test suite!)
If /dev/tty
is present but can't be opened, age will now fallback to trying to treat stdin as a terminal as if /dev/tty
wasn't present. (Thanks @brandsimon!)
Input prompts now go to the terminal, even if standard error is redirected.
Values of the new armor.Error
type are now returned wrapped in decryption errors when appropriate.
Windows binary releases are now signed. (Thanks @technion!)
Documentation and error messages were improved.
age v1.1.0-rc.1: plugin and YubiKeys support
age is a simple, modern and secure file encryption tool, format, and Go library. It features small explicit keys, no config options, and UNIX-style composability. Learn more by reading the README, the age(1) man page, the Go API reference, the format specification, or the full release changelog. Watch the repository or follow @FiloSottile on Twitter to be notified of new releases.
v1.1.0-rc.1 is the first release candidate of v1.1.0. Users are encourage to test the new release and especially the new features listed below. Issue or UX reports in advance of the final release are greatly appreciated.
📃 In case you missed it: a new, more polished version of the age format specification has been published.
Plugin support
The age CLI now supports plugins, such as age-plugin-yubikey by @str4d. To test it on macOS with Homebrew:
$ brew install --HEAD age
$ brew install age-plugin-yubikey
$ age-plugin-yubikey # interactive setup
$ age -r age1yubikey1qwt50d05nh5vutpdzmlg5wn80xq5negm4uj9ghv0snvdd3yysf5yw3rhl3t
$ age -d -i age-yubikey-identity-388178f3.txt
Plugins must be loaded explicitly by using their respective recipient or identity, and are not tied to a specific header stanza type. This means plugins can be used not only to support new recipient types such as PIV tokens (i.e. YubiKeys) or cloud KMS solutions, but also to produce passphrase-encrypted files that can be decrypted without plugins, to store age native private keys on secure elements, or even for agent functionality or to proxy decryption operations to remote machines.
Plugins operate over a simple textual stdin/stdout protocol (C2SP/C2SP#5). Developers are encouraged to reach out with plugin ideas and announcements. Read more in the relevant man page section.
CLI breaking changes
If -i
is used, passphrase-encrypted files are now rejected. Previously, a passphrase-encrypted file was auto-detected and the identity file was ignored. This could lead to unexpected behavior, such as a script blocking for user interaction, based on potentially untrusted input files. Now, age -d
must be invoked without -i
arguments to decrypt passphrase-encrypted files. A helpful error is printed otherwise. This should not break any automated system as passphrase decryption was always interactive.
Empty final chunks are now rejected. If a payload was a multiple of 64KiB long, there were two valid encryptions for it: with a "full" last chunk encrypting 64KiB, or with an additional "empty" chunk encrypting 0 bytes. age, rage, and all other known implementations only ever produced the former. (Note that age will forever decrypt files it generated.) The latter is now rejected. The specification is being updated (C2SP/C2SP#13) and test cases will be provided.
Minor changes
If /dev/tty
is present but can't be opened, age will now fallback to trying to treat stdin as a terminal as if /dev/tty
wasn't present.
Windows binary releases are now signed.
Documentation and error messages were improved.
age v1.0.0 🏁
age—pronounced [aɡe̞]
, like the Italian “aghe”—is a simple, modern and secure file encryption tool, format, and Go library.
It features small explicit keys, no config options, and UNIX-style composability.
$ age-keygen -o key.txt
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
$ tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age
$ age --decrypt -i key.txt data.tar.gz.age > data.tar.gz
v1.0.0 is the first stable release of the Go CLI and API, twenty months after the first beta.
Learn more by reading the README, the age(1) man page, the Go API reference, or the original design document.
Watch the repository or follow @FiloSottile on Twitter to be notified of new releases.
Never two without three
Maybe™️ actually™️ the last v1.0.0 release candidate!
Encrypted identity files are now supported. Regular passphrase-encrypted age files can be used with -i
, the passphrase will be requested interactively, and the decrypted file will be read line-by-line as a standard identity file.
Passphrases can now be requested interactively from the terminal on Windows even if standard input is in use.
Errors are now tidier and all start with age: error:
.
The last (?) v1.0.0 release candidate!
This is it! The v1.0.0 milestone is empty. Will let it simmer for a bit and then we'll have v1.0.0. Please test it!
Main changes
🤏 Reject RSA keys (for ssh-rsa
) smaller than 2048 bits (#266)
🥞 Remove limit of 20 recipients per file (#139)
📜 Add age(1) and age-keygen(1) man pages (#131) — read them here!
⚔️ Fix armored encoding for files of certain lengths (#263)
Minor changes
v1.0.0 release candidate!
This release includes the last minor changes to the CLI, improved error messages, and an expanded test suite.
- The output file is now overwritten if it already exists, consistently with most UNIX tools.
- There is now an optional
-e
/--encrypt
flag to match-d
/--decrypt
. When it's explicitly specified, it's now possible to use-i
/--identity
to encrypt symmetrically to an identity file. - The new
age-keygen
-y
mode converts an identity file into the corresponding recipients.
Breaking changes to the Identity and Recipient interfaces
The two interfaces Recipient
and Identity
went from
type Identity interface {
Type() string
Unwrap(block *Stanza) (fileKey []byte, err error)
}
type Recipient interface {
Type() string
Wrap(fileKey []byte) (*Stanza, error)
}
to
type Identity interface {
Unwrap(stanzas []*Stanza) (fileKey []byte, err error)
}
type Recipient interface {
Wrap(fileKey []byte) ([]*Stanza, error)
}
This is a better abstraction for interacting with complex implementations like plugins, as explained in the commit messages.
Most age applications (including all the public ones) use implementations provided by the age module itself, so they won't be affected by this breaking change. If third-party code implemented custom recipients or identities, they will need to update.
The IdentityMatcher
interface was also removed, a positive side-effect of this change.
The header format was also changed to expect a short line at the end of every stanza, which allows the format to be used in a streaming protocol. No encrypted files produced by cmd/age are affected. A small number of encrypted files produced by rage are affected, and won't decrypt with newer versions of age. They still decrypt with any version of rage.
Finally, an NoIdentityMatchError
type was added, to detect the specific Decrypt
error condition.
This is hopefully the first and last breaking change before v1.0.0 is released.