Implement custom Recipients and Identities
mainRecipient and Identity interfaces. This enables integration with remote APIs, secrets managers, or other age plugins.repository·main·Indexed 19 days ago
https://github.com/filosottile/typageA TypeScript implementation of the age file encryption format. It supports asymmetric keys, passphrases, post-quantum hybrid keys, and WebAuthn-based symmetric encryption for browsers. The library provides Encrypter and Decrypter classes, support for the Streams API, and utilities for ASCII armor encoding. It is compatible with Node.js 20+, Bun, Deno, and modern browsers.
Recipient and Identity interfaces. This enables integration with remote APIs, secrets managers, or other age plugins.You can install age-encryption via npm or JSR. It is compatible with Node.js 20+, Bun, Deno, and modern browsers.
npm install age-encryptiondeno add jsr:@age/age-encryptionIf you encrypt files in the browser using a FIDO2 security key, you can decrypt them in the CLI using the age-plugin-fido2prf plugin. Since WebAuthn encryption is symmetric, use the -i flag with the identity string to encrypt/decrypt.
go install filippo.io/typage/fido2prf/cmd/age-plugin-fido2prf@latest
# Decrypt using an identity file
age -d -i identity.txt << EOF
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
EOFTo use age-encryption in a browser via a <script> tag, you can bundle it using esbuild to create a global age variable.
cd "$(mktemp -d)" && npm init -y && npm install esbuild age-encryption
npx esbuild --target=es2022 --bundle --minify --outfile=age.js --global-name=age age-encryptionYou can use a passphrase instead of a key pair by using the Scrypt implementations.
ScryptRecipient (Encryption):
new ScryptRecipient(passphrase: string, logN: number): Requires a passphrase and a work factor logN.ScryptIdentity (Decryption):
new ScryptIdentity(passphrase: string): Requires the passphrase used during encryption.const recipient = new ScryptRecipient("my-password", 14);
const identity = new ScryptIdentity("my-password");For advanced use cases like integrating with hardware security modules (HSMs), remote APIs, or secrets managers, you can implement the Identity and Recipient interfaces.
IdentityUsed during decryption to unwrap the file key.
unwrapFileKey(stanzas: Stanza[]): Uint8Array | null | Promise<Uint8Array | null>Uint8Array file key if the provided stanzas match your identity. Return null if they do not match. Throw an error only if the stanza matches but is malformed or decryption fails due to external factors (e.g., network error).RecipientUsed during encryption to wrap the file key.
wrapFileKey(fileKey: Uint8Array): Stanza[] | Promise<Stanza[]>Stanza objects that contain the encrypted file key for this recipient.These classes provide support for tag-based recipients.
TagRecipient: Uses p256tag (P-256) and requires a recipient string starting with age1tag1....HybridTagRecipient: Uses mlkem768p256tag (Hybrid P-256) and requires a recipient string starting with age1tagpq1....For post-quantum resistance, use the hybrid implementations which utilize MLKEM768X25519.
HybridRecipient: Wrap file keys using a recipient string starting with age1pq1....HybridIdentity: Unwrap file keys using an identity string starting with AGE-SECRET-KEY-PQ-1....const recipient = new HybridRecipient("age1pq1...");
const identity = new HybridIdentity("AGE-SECRET-KEY-PQ-1...");For large files or on-the-fly processing, age-encryption supports ReadableStream. The Encrypter.encrypt method can accept a stream and returns a stream. The Decrypter.decrypt method can accept an encrypted stream and returns a decrypted stream.
import { Encrypter, Decrypter } from "age-encryption"
const file = new File([new TextEncoder().encode("age")], "age.txt")
const e = new Encrypter()
e.setScryptWorkFactor(12)
e.setPassphrase("your-passphrase")
const encryptedStream = await e.encrypt(file.stream())
const d = new Decrypter()
d.addPassphrase("your-passphrase")
const decryptedStream = await d.decrypt(encryptedStream)
console.log(await new Response(decryptedStream).text())For symmetric encryption using a passphrase, use setPassphrase on the Encrypter and addPassphrase on the Decrypter.
import { Encrypter, Decrypter } from "age-encryption"
const e = new Encrypter()
e.setPassphrase("your-passphrase")
const ciphertext = await e.encrypt("Hello, age!")
const d = new Decrypter()
d.addPassphrase("your-passphrase")
const out = await d.decrypt(ciphertext, "text")Use generateHybridIdentity() instead of generateIdentity() to create identities that support post-quantum hybrid key exchange.
import * as age from "age-encryption"
const identity = await age.generateHybridIdentity()
const recipient = await age.identityToRecipient(identity)
// ... use Encrypter and Decrypter as usualTo use standard age asymmetric encryption, generate an identity (private key) and convert it to a recipient (public key). Use the Encrypter class to add recipients and the Decrypter class to add identities.
import * as age from "age-encryption"
const identity = await age.generateIdentity()
const recipient = await age.identityToRecipient(identity)
const e = new age.Encrypter()
e.addRecipient(recipient)
const ciphertext = await e.encrypt("Hello, age!")
const d = new age.Decrypter()
d.addIdentity(identity)
const out = await d.decrypt(ciphertext, "text")
console.log(out)