Exactly six years after the first age beta release, v1.3.0 brings post-quantum resistance to age, along with a couple long-requested features, built-in support for recipients compatible with hardware plugins, I/O API improvements, and many usability enhancements.
Post-quantum recipients
age now has native post-quantum recipients based on HPKE with a hybrid ML-KEM-768 KEM. The recipients start with age1pq1..., and the identities start with AGE-SECRET-KEY-PQ-1....
To generate a post-quantum keypair:
$ age-keygen -pq
If you have your own age implementation, C2SP has the specification, and CCTV has test vectors for the new hybrid recipient types.
(If you are using an older age client, an optional plugin is available that provides out-of-the-box support for encryption to hybrid recipients. Hybrid identities can be converted to work with the plugin with age-plugin-pq -identity.)
New I/O APIs
The new DecryptReaderAt API implements seeking decryption, which can be used with zip.NewReader.
The new EncryptReader API implements pull-based encryption by wrapping an io.Reader, as opposed to wrapping an io.Writer like Encrypt.
age-inspect
The new age-inspect(1) tool presents the metadata of an age file without decrypting it.
hello.age is an age file, version "age-encryption.org/v1".
This file is ASCII-armored.
This file is encrypted to the following recipient types:
- "mlkem768x25519"
This file uses post-quantum encryption.
Size breakdown (assuming it decrypts successfully):
Header 1627 bytes
Armor overhead 1350 bytes
Encryption overhead 32 bytes
Payload 1959 bytes
-------------------
Total 4968 bytes
Tip: for machine-readable output, use --json.
Built-in recipients compatible with hardware plugins
age(1) now supports encrypting to age1tag1... and age1tagpq1... recipients, which are compatible with hardware plugins. The latest or upcoming versions of age-plugin-yubikey, age-plugin-tpm, and age-plugin-se will support producing these recipients for existing and new keys, and decrypting files encrypted to them.
The filippo.io/age/tag package provides an implementation, including a Recipient.Tag method useful to plugin implementations.
C2SP has the specification for these recipient types.
(For users that are using older age clients, there are compatiblity plugins that can be shipped along with the hardware plugins.)
Non-interactive passphrase input
We have long resisted implementing non-interactive CLI passphrase input, because most use cases are more securely and/or efficiently served by native keys or passphrase-encrypted identities. However, there are some residual use cases.
This is now available through the batchpass plugin, but we invite everyone to read the warning in the man page and help text.
Go framework for implementing plugins
The filippo.io/age/plugin package now provides a framework for exposing age.Recipient and age.Identity implementations as standalone CLI plugins.
For example, this is the entire age-plugin-tagpq compatibility implementation:
package main
import (
"log"
"os"
"filippo.io/age"
"filippo.io/age/plugin"
"filippo.io/age/tag"
)
func main() {
p, err := plugin.New("tagpq")
if err != nil {
log.Fatal(err)
}
p.HandleRecipient(func(b []byte) (age.Recipient, error) {
return tag.NewHybridRecipient(b)
})
os.Exit(p.Main())
}Moreover, the new plugin.NewTerminalUI function provides a ready-to-use terminal-based ClientUI implementation for the existing plugin client API.
Armor fixes
The CLI now allows some leading whitespace before the armor header, as the API always did. The API and CLI now reject empty lines in armored data, as required by the spec.
Detached headers
The new set of APIs ExtractHeader, DecryptHeader, and NewInjectedFileKeyIdentity allow working with detached age headers, where the file key is unwrapped separately from the actual file decryption. This can be useful for efficiency or to delegate unwrapping without exposing the file contents.
Usability improvements
The CLI will now refuse to output decrypted binary data to the terminal. (We did this before attending the gpg.fail talk!)
Native identities are now tried first when decrypting through both the CLI and the API, since they can't require interaction. Order is otherwise preserved.
NoIdentityMatchError now exposes the stanza types of the header, unwraps to the errors returned by the Identity.Unwrap method calls, and prints the underlying error if only one identity was provided.
Virtual terminal processing is now enabled on Windows terminals that don't enable it by default, so that ANSI escape sequences work as expected.
Many improved and more proactively helpful error messages.
The release artifacts now include a source tarball with vendored dependencies.
