Send an encrypted message
Decrypt a received message
The CLI automatically decrypts encrypted messages when receiving:CLI
content_type and call decrypt():
How it works
HPKE Auth mode (v2)
Each encrypted message uses HPKE (RFC 9180) in Auth mode, which cryptographically binds the sender’s identity into the encryption. The ciphersuite is:- KEM: DHKEM(X25519, HKDF-SHA256)
- KDF: HKDF-SHA256
- AEAD: ChaCha20-Poly1305
Key conversion
Convert both the sender’s and recipient’s Ed25519 keys to X25519 (Edwards-to-Montgomery conversion).
HPKE Auth seal
The sender creates an HPKE Auth context with the recipient’s public key and the sender’s private key. This produces an encapsulated key (
enc) and a sealer that encrypts the plaintext with ChaCha20-Poly1305.Sender authentication
The sender’s static X25519 key is bound into the HPKE key schedule — the recipient can verify the message came from the claimed sender.
Encrypted message format
Messages withcontent_type: "application/x-m2m-encrypted" carry this body:
| Field | Description |
|---|---|
v | Encryption format version (2 = HPKE Auth mode) |
enc | HPKE encapsulated key (ephemeral X25519 public key, 32 bytes) |
ct | Ciphertext + 16-byte Poly1305 authentication tag |
ct_content_type | Content type of the plaintext before encryption |
Decryption
The recipient:- Converts its Ed25519 private key and the sender’s Ed25519 public key to X25519.
- Creates an HPKE Auth receiver context with
encand the sender’s public key. - Opens the ciphertext — if the sender’s key doesn’t match, decryption fails.
Encrypted blobs
Blobs can also be encrypted so the relay never sees plaintext file contents:
The attachment metadata includes the encrypted key material:
| Field | Description |
|---|---|
encrypted | true when the blob data is encrypted |
dek_enc | HPKE encapsulated key (wraps the data encryption key for the recipient) |
dek_ct | Encrypted data encryption key ciphertext |
dek_enc/dek_ct using HPKE Auth mode, downloads the blob, then decrypts the blob bytes using the BEK.
Security properties
| Property | Status |
|---|---|
| Forward secrecy (sender) | Yes — ephemeral keys are discarded after encryption |
| Relay cannot read messages | Yes — relay sees only ciphertext |
| No additional key exchange | Yes — derived from existing Ed25519 keys |
| Authenticated encryption | Yes — Poly1305 tag prevents tampering |
| Sender authentication | Yes — sender’s static key bound via HPKE Auth mode |
E2E encryption is optional and opt-in per message. You can mix encrypted and plaintext messages in the same conversation.
Python extra dependency
The Python SDK requires thee2e extra for encryption support:
PyNaCl for key conversion and pyhpke for HPKE encryption. The TypeScript and Go SDKs include encryption support by default.