I have some rudimentary cryptography knowledge but am by no means an expert.

I generally understand stream ciphers, such as such as ChaCha20-Poly1305, to be symmetric. I am wondering how age (https://github.com/FiloSottile/age) uses public and private keys to encrypt data with ChaCha20-Poly1305. Is it similar to how in some protocols asymmetric encryption is used to establish a shared symmetric key, or is age’s protocol entirely different? If so, what is the asymmetric algorithm used to share the symmetric key?

If there’s a broader concept that I’m missing here, I would love any recommendations for introductory further reading.

  • 2
    $\begingroup$ It's X25519 key exchange between an ephemeral key pair and the recipient's public key. Yes, it's a standard approach that is used widely elsewhere $\endgroup$
    – knaccc
    Feb 28, 2023 at 6:32
  • 1
    $\begingroup$ It seems that scrypt is also an option as "recipient type". For the protocol for X25519 it seems that the identity is the party that decrypts using the static private key (also called identity, nice), and recipient consists of the static public key. The ephemeral keys are called ephemeral secret which is what we normally call a private key and the public key is called ephemeral share (ugh), which are used to calculate an shared secret. I'm not sure who was naming things here, but it is very confusing. Must be the "stanza" thing or something, poetic :| $\endgroup$
    – Maarten Bodewes
    Feb 28, 2023 at 10:09

1 Answer 1


The specification for age is here. As explained in the X25519 recipient stanza section, an ephemeral key pair is generated, and the ephemeral private key is used to perform an X25519 key exchange with the recipient public key. This computes a shared secret that's used to derive a key using HKDF-SHA-256. Then the ephemeral public key is included in the encrypted file to allow the recipient to compute the same shared secret using their private key, enabling decryption.

ephemeral secret = read(CSPRNG, 32)
ephemeral share = X25519(ephemeral secret, basepoint)

salt = ephemeral share || recipient
info = "age-encryption.org/v1/X25519"
shared secret = X25519(ephemeral secret, recipient)
wrap key = HKDF-SHA-256(ikm = shared secret, salt, info)
body = ChaCha20-Poly1305(key = wrap key, plaintext = file key)

The downside of this approach is that it provides no sender authentication. It's like libsodium's sealed box in that the sender's identity is anonymous. In the context of age, this can be considered a design flaw because if you receive a file from someone, you want to know that it came from them and was not replaced by an attacker.

The best further reading on this topic is a series of blog posts by Neil Madden on public key authenticated encryption. Part 2, which I've linked above, explains what age is doing and better alternatives.

Additionally, I would strongly recommend Real-World Cryptography by David Wong, which is by far the best introductory book I've come across and has some facts/stories even more experienced people may not know. It covers Curve25519/X25519 and hybrid encryption with ECIES like age is doing.

Another important thing to read is the Curve25519/X25519 RFC, particularly the Security Considerations section, which has an important detail that's often overlooked. There's the original paper. Finally, there are various blog posts like this and this.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.