The Direct Message Extension introduces a secure way for two users to publish encrypted messages to each other within the twtxt ecosystem. Direct messages (DMs) leverage public/private key cryptography to ensure that only the intended recipient can read the message. This extension also enables metadata to specify a user’s public key, ensuring compatibility with encryption protocol (Curve25519).
Overview of Direct Message Format
Direct messages are encoded and include the recipient’s username and the encrypted message payload. This allows the message to remain private while adhering to the structure and principles of the twtxt format.
A typical direct message will contain the following structure:
!<nick url> <encrypted_message>
Here:
nick
is the intended recipient’s nickname.url
is the public URL to the recipient’s twtxt feed.<encrypted_message>
is the ciphertext encrypted using the recipient’s public key.
Requirements
This extension requires the following steps to send secure direct messaging:
-
Create Public and Private Key:
openssl genpkey -algorithm X25519 -out private_key.pem openssl pkey -pubout -in private_key.pem -out public_key.pem
-
Public Key Declaration: A new metadata field named
public_key
must be present in the user’s twtxt feed. This field should contain the user’s Public Key in Base64 format.Example:
base64 -w 0 < public_key.pem > public_key.b64
The output, for example, will be
MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
.Add the public key to the twtxt feed metadata:
# nick = example # url = https://example.com/twtxt.txt # avatar = https://example.com/avatar.png # description = An example feed # public_key = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
-
Encryption of DMs: Direct messages must be encrypted using the recipient’s public key to ensure confidentiality. Any modern cryptographic library supporting EC (libsocium, nacl, Go’s box/secretbox, etc.) public/private key encryption can be used for this purpose.
wcurl https://recipient.com/twtxt.txt | grep public_key | cut -d' ' -f4 > recipient_public_key.pem openssl pkeyutl -derive -inkey sender_private_key.pem -peerkey recipient_public_key.pem -out shared_key.bin echo -n "My directo message" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -out message.enc -pass file:shared_key.bin base64 -w 0 < message.enc > message.enc.b64
The output, for example, will be
1234567890abcdef=
. -
DM Syntax: Direct messages must explicitly target the recipient using the
!<nick url>
syntax before the encrypted payload.Example:
2025-01-18T18:20:00Z !<joe https://example.com/twtxt.txt> 1234567890abcdef=
-
Notify recipient (Optional): You can make a request using the Multi-User User-Agent Extension.
Format Details
Public Key Metadata
The mandatory public_key
metadata field allows feed authors to publish their public key. Clients can use this key to encrypt messages for the feed author. The key must be a string. This ensures that only the author can decrypt the message using their private key.
# public_key = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
Encryption Process
When sending a direct message:
- Retrieve the recipient’s public key from their
public_key
metadata. - Encrypt the plaintext message using the public key.
- Encode the ciphertext (Base64).
- Add the new line to the twtxt feed with the recipient’s
nick
andurl
followed by the encrypted message.
Example Workflow
Let’s assume two users, alice
and bob
, want to communicate privately. Alice wants to send a direct (she likes to encrypt the message) and Bob wants to read it (decrypt the message).
-
Key Generation: Alice and Bob each generate a public/private key pair using Curve25519 algorithm with
openssl
:openssl genpkey -algorithm X25519 -out alice_private_key.pem openssl pkey -pubout -in alice_private_key.pem -out alice_public_key.pem
openssl genpkey -algorithm X25519 -out bob_private_key.pem openssl pkey -pubout -in bob_private_key.pem -out bob_public_key.pem
-
Setup:
Alice declares her public key in her
twtxt.txt
metadata:Open
alice_public_key.pem
and copy the key.-----BEGIN PUBLIC KEY----- MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI= -----END PUBLIC KEY-----
The public key is the string between
-----BEGIN PUBLIC KEY-----
and-----END PUBLIC KEY-----
:MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
.And paste it in the
public_key
field.# nick = alice # url = https://alice.example.com/twtxt.txt # public_key = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
Bob does the same in his feed:
# nick = bob # url = https://bob.example.com/twtxt.txt # public_key = MCowBQYDK2VuAyEALBmCrrkDzfUbo3CBBvHB8R9CCD3U88xzQcohBZRGMzE=
-
Encrypting the Message:
Bob wants to send a private message to Alice. Using Alice’s
public_key
, Bob encrypts his message using Openssl:- First, Bob retrieves Alice’s public key:
curl https://alice.example.com/twtxt.txt | grep public_key | cut -d' ' -f4 > alice_public_key.pem
- Calculate the share key:
Bob use his private key and Alice’s public key to calculate the shared key:
openssl pkeyutl -derive -inkey bob_private_key.pem -peerkey alice_public_key.pem -out shared_key.bin
Then, Bob encrypts the message using the shared key. The message is storage in
message.enc
:echo -n "Hi Alice, let's meet tomorrow at 5 PM!" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -out message.enc -pass file:shared_key.bin
- Bob encodes the encrypted message in Base64:
base64 -w 0 < message.enc > message.enc.b64
Check
message.enc.b64
for the encrypted message:U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==
This will be the string of the direct message.
-
Sending the Message:
Bob appends the encrypted message to his feed:
2025-01-18T18:20:00Z !<alice https://alice.example.com/twtxt.txt> U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==
-
Reading the Message: Alice’s client fetches the message from Bob’s feed and review if the message is for her. She would see
!<alice https://alice.example.com/twtxt.txt>
.Using her private key, Alice decrypts the message:
- First, Alice retrieves Bob’s public key:
curl https://bob.example.com/twtxt.txt | grep public_key | cut -d' ' -f4 > bob_public_key.pem
- Calculate the share key:
Alice use her private key and Bob’s public key to calculate the shared key:
openssl pkeyutl -derive -inkey alice_private_key.pem -peerkey bob_public_key.pem -out shared_key.bin
- Alice decodes the encrypted message from Base64:
echo 'U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==' | base64 -d > message_from_bob.enc
- Alice decrypts the message using the shared key.
openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -in message_from_bob.enc -out message_from_bob.txt -pass file:shared_key.bin
Check
message_from_bob.txt
for the decrypted message.cat message_from_bob.txt
Resulting in the plaintext message:
Hi Alice, let's meet tomorrow at 5 PM!
Security Considerations
-
Key Verification: Users should ensure public keys are genuine and match the feed owner to mitigate man-in-the-middle attacks.
-
Encrypted Payload Visibility: While the payload is encrypted, all users viewing the feed will see the recipient
nick
andurl
. This preserves openness but prevents eavesdroppers from reading the message. -
Private Key Security: Users must securely store their private keys and avoid exposing them. A compromised private key can lead to the decryption of all previously sent messages.
-
Replay Attacks: Clients should implement measures to detect and ignore duplicate messages to prevent replay attacks.
Supported Twtxt Clients
For clients to support the Direct Message Extension, they must:
- Parse metadata to retrieve the
public_key
field. - Implement encryption and decryption using public/private key cryptography.
- Render direct messages prefixed with
!<nick url>
. - If a client does not support this extension, it should ignore direct messages.
Changelog
- 2025-01-18: Initial draft.