Documentation

How AgePony works.

A reference for the formats and flows. For step-by-step walkthroughs, see the guides.

Recipient formats

Anywhere AgePony asks for a recipient, it accepts:

  • age recipient: age1…, a native X25519 public key.
  • post-quantum recipient: age1pq1…, a hybrid of ML-KEM-768 and X25519. About 1,950 characters, because it carries both keys.
  • SSH public key: ssh-ed25519 … or ssh-rsa ….
  • GitHub username: AgePony fetches github.com/<user>.keys and encrypts to every SSH key found.
  • passphrase: no keypair, the file is sealed with a scrypt-stretched passphrase.

Post-quantum recipients

A post-quantum recipient uses the mlkem768x25519 stanza type: ML-KEM-768 (FIPS 203) combined with classical X25519, so the file stays sealed as long as either half holds. Generate one under Identities, or paste someone else's age1pq1… string.

One rule the app enforces: a post-quantum recipient cannot be combined with a classical one on the same file. Mixing them would mean the file opens to whichever key breaks first, so the weaker recipient would silently set the bar for everyone. Encrypt twice if you genuinely need both.

On the other end, the recipient needs age v1.3.0 or newer, or AgePony 3.0 or newer. Older versions will not recognise the stanza.

Re-encrypting to a new key

Encrypting new files to a quantum-safe key does nothing for files already encrypted and already copied. The re-encrypt tool decrypts a file you can open and seals it to new recipients. Two honest limits: your original file is left exactly as it was, and copies already in someone else's hands stay on the old key. While the operation runs, a decrypted copy exists in the app's temporary storage, and it is deleted as soon as the re-encrypt finishes or fails.

A signed file keeps its signature through a re-encrypt. The signature covers the payload, so rewrapping it for new recipients leaves it valid.

Encrypting

Encrypting to one or more recipients writes a standard .age file. Picking several input files asks whether you want one archive or one encrypted file each. An archive is an uncompressed USTAR bundle.tar encrypted to a single bundle.tar.age; recover the originals anywhere with tar -xf bundle.tar after decrypting.

File size

There is no practical limit. Every path streams: bytes move through fixed 64 KiB buffers, so memory use is flat regardless of file size and a 1 GB file costs the same working memory as a 1 KB one. Archives are produced lazily as the encryptor reads, so a multi-file archive is never written to disk before being encrypted.

The one place cost is deliberately high is passphrase mode. scrypt is memory-hard on purpose, and its work factor sets how much memory the key derivation itself needs, independent of the file. That factor is adjustable in Settings; higher resists guessing better and costs more time and memory.

Inspecting a file

Inspect reads only an age file's header. It reports the recipient stanzas present, whether any of them is post-quantum, whether the file is passphrase-protected and at what work factor, and whether the file matches an identity you hold. Nothing is decrypted, so it is instant on a file of any size.

Decrypting

Open a .age file from Files and choose "Open in AgePony", or paste an armored block into the Text tab. AgePony matches the file's stanzas against the identities in your vault, or prompts for a passphrase if it's a scrypt file.

Signing

All AgePony signatures are SSHSIG under the namespace agepony. There are two shapes.

Detached. Signing on its own produces a separate .sig file. Anyone with OpenSSH verifies it, no AgePony required.

Sealed inside (3.0 and later). Signing and encrypting in one step produces a single .age file. Inside it is a signed bundle: a small uncompressed USTAR tar holding the payload plus a detached SSHSIG over that payload, with the whole tar then encrypted. The signature is over the plaintext, and it is not visible in the ciphertext, so an interceptor learns nothing about who sent the file. AgePony verifies it automatically as the file decrypts.

Choose the signing key:

  • in-app SSH key: ed25519 or RSA from your vault.
  • Secure Enclave: a P-256 key generated in hardware, non-exportable.
  • FIDO security key: tap over NFC, produces sk-ssh-ed25519 or sk-ecdsa-sha2-nistp256. Available for detached signing; the combined sign-and-encrypt flow uses in-app and Secure Enclave keys.

Post-quantum identities cannot sign. ML-KEM is a key encapsulation mechanism, not a signature scheme, so a post-quantum key can receive files but never sign one.

Verifying

A signed bundle verifies itself: decrypt it in AgePony and the verdict appears alongside the decrypted file, with the signer attributed against your identities, trusted signers and saved recipients. For a detached signature, the Verify action takes a file, its signature, and an allowed signer. Anywhere else, OpenSSH verifies a detached signature:

ssh-keygen -Y verify -f allowed_signers \ -I signer@example.com -n agepony \ -s file.sig < file

Interoperability

Files are bit-perfect with the reference age CLI in both directions. Post-quantum recipients need age v1.3.0 or newer on the other end. Detached signatures are verifiable by stock OpenSSH.

The one AgePony-specific structure is the signed bundle, and it is deliberately boring: a plain USTAR tar with three entries, so even without AgePony you can decrypt with age, run tar -xf, and get the payload plus a standard .sig you verify with ssh-keygen. Nothing is locked in. The Android build writes the same format byte for byte.

Where things live

Identities and recipients live in an encrypted vault in the app sandbox; the master key sits in the platform keystore behind biometrics. Nothing is stored on a server, because there is no server. See Security for the full model.