This tutorial shows how Mau works at the lowest level using only GPG commands. Understanding these primitives will help you appreciate what Mau does behind the scenes.
Time: ~15 minutes
Prerequisites: GPG installed (gpg --version)
Mau is built on PGP/GPG. Before using Mau’s tools, it’s valuable to understand:
Generate a PGP key pair:
1gpg --full-generate-key
Follow the prompts:
(9) ECC (sign and encrypt) *default: Curve 25519* (recommended, Ed25519)
(1) RSA and RSA with 4096 bits for compatibility0 (no expiration) or your preferenceNote: Mau defaults to Ed25519 keys (faster, smaller, modern crypto). RSA 4096 still works for compatibility.
Get your fingerprint:
1gpg --fingerprint your-email@example.com
Output example (Ed25519):
pub ed25519 2026-03-02 [SC]
5D00 0B2F 2C04 0A16 75B4 9D7F 0C7C B7DC 3699 9D56
uid [ultimate] Alice <alice@example.com>
Export fingerprint without spaces:
1export MY_FPR="5D000B2F2C040A1675B49D7F0C7CB7DC36999D56"
2echo "My fingerprint: $MY_FPR"
What just happened?
Mau uses a simple directory layout:
1mkdir -p ~/.mau/$MY_FPR # Your posts go here
2mkdir -p ~/.mau/.mau # Keys and metadata
Export your private key (encrypted with a passphrase):
1gpg --export-secret-keys --armor $MY_FPR | \
2 gpg --symmetric --armor --output ~/.mau/.mau/account.pgp
Enter a passphrase to protect this file. This is your account backup.
Create a social media post (JSON-LD with Schema.org vocabulary):
1cat > /tmp/hello.json <<'EOF'
2{
3 "@context": "https://schema.org",
4 "@type": "SocialMediaPosting",
5 "headline": "Hello from the decentralized web!",
6 "articleBody": "This post is encrypted, signed, and stored as a file on my disk.",
7 "author": {
8 "@type": "Person",
9 "name": "Alice",
10 "identifier": "5D000B2F2C040A1675B49D7F0C7CB7DC36999D56"
11 },
12 "datePublished": "2026-02-28T07:00:00Z"
13}
14EOF
Sign and encrypt the post (for yourself, making it public):
1gpg --sign --encrypt --recipient $MY_FPR \
2 --output ~/.mau/$MY_FPR/hello.json.pgp \
3 /tmp/hello.json
What just happened?
.pgp file (OpenPGP message format)Verify it exists:
1ls -lh ~/.mau/$MY_FPR/hello.json.pgp
Decrypt and verify:
1gpg --decrypt ~/.mau/$MY_FPR/hello.json.pgp
Output:
1{
2 "@context": "https://schema.org",
3 "@type": "SocialMediaPosting",
4 ...
5}
6gpg: Signature made Sat 28 Feb 2026 07:00:00 AM CET
7gpg: using RSA key 5D000B2F...
8gpg: Good signature from "Alice <alice@example.com>" [ultimate]
What does “Good signature” mean?
Let’s create a second identity to understand peer interaction:
1# Generate Bob's key (use different email)
2gpg --batch --passphrase '' --quick-generate-key "Bob <bob@example.com>" rsa4096
3
4# Get Bob's fingerprint
5export BOB_FPR=$(gpg --list-keys --with-colons bob@example.com | awk -F: '/^fpr:/ {print $10; exit}')
6echo "Bob's fingerprint: $BOB_FPR"
7
8# Create Bob's directory
9mkdir -p ~/.mau/$BOB_FPR
Create a private message (encrypted only for Bob):
1cat > /tmp/private-msg.json <<EOF
2{
3 "@context": "https://schema.org",
4 "@type": "Message",
5 "text": "Hey Bob, this is a private message only you can read!",
6 "sender": {
7 "@type": "Person",
8 "identifier": "$MY_FPR"
9 },
10 "recipient": {
11 "@type": "Person",
12 "identifier": "$BOB_FPR"
13 },
14 "dateSent": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
15}
16EOF
Encrypt for Bob (and sign with your key):
1gpg --sign --encrypt --recipient $BOB_FPR \
2 --output ~/.mau/$MY_FPR/msg-to-bob.json.pgp \
3 /tmp/private-msg.json
Try to read it as yourself:
1gpg --decrypt ~/.mau/$MY_FPR/msg-to-bob.json.pgp
You’ll get:
gpg: decryption failed: No secret key
Why? Because you encrypted it for Bob’s public key. Only Bob’s private key can decrypt it.
Now read as Bob:
1gpg --decrypt --local-user bob@example.com ~/.mau/$MY_FPR/msg-to-bob.json.pgp
Success! Bob can read the message, and GPG confirms it was signed by you.
Bob can comment on your post:
1cat > /tmp/comment.json <<EOF
2{
3 "@context": "https://schema.org",
4 "@type": "Comment",
5 "text": "Great post, Alice!",
6 "author": {
7 "@type": "Person",
8 "name": "Bob",
9 "identifier": "$BOB_FPR"
10 },
11 "about": "/p2p/$MY_FPR/hello.json",
12 "dateCreated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
13}
14EOF
15
16# Bob signs and encrypts for himself (public comment)
17gpg --sign --encrypt --recipient $BOB_FPR \
18 --local-user bob@example.com \
19 --output ~/.mau/$BOB_FPR/comment-alice.json.pgp \
20 /tmp/comment.json
Bob’s comment references your post using the Mau address: /p2p/<fingerprint>/<filename>
Create a post visible to both you and Bob:
1cat > /tmp/group-post.json <<'EOF'
2{
3 "@context": "https://schema.org",
4 "@type": "SocialMediaPosting",
5 "headline": "Weekend plans?",
6 "articleBody": "Anyone up for hiking on Saturday?",
7 "datePublished": "2026-02-28T08:00:00Z"
8}
9EOF
10
11# Encrypt for multiple recipients
12gpg --sign --encrypt \
13 --recipient $MY_FPR \
14 --recipient $BOB_FPR \
15 --output ~/.mau/$MY_FPR/group-post.json.pgp \
16 /tmp/group-post.json
Now both you and Bob can decrypt this file. This is how Mau implements group chats!
When you edit a post, Mau keeps old versions:
1# Create a versions directory
2mkdir -p ~/.mau/$MY_FPR/hello.json.pgp.versions
3
4# Hash the current version
5CURRENT_HASH=$(sha256sum ~/.mau/$MY_FPR/hello.json.pgp | cut -d' ' -f1)
6echo "Current version hash: $CURRENT_HASH"
7
8# Move current version to versions directory
9cp ~/.mau/$MY_FPR/hello.json.pgp \
10 ~/.mau/$MY_FPR/hello.json.pgp.versions/$CURRENT_HASH.pgp
11
12# Create new version (edited)
13cat > /tmp/hello-v2.json <<'EOF'
14{
15 "@context": "https://schema.org",
16 "@type": "SocialMediaPosting",
17 "headline": "Hello from the decentralized web! [EDITED]",
18 "articleBody": "This post is encrypted, signed, and stored as a file. I can edit it!",
19 "datePublished": "2026-02-28T07:00:00Z",
20 "dateModified": "2026-02-28T08:00:00Z"
21}
22EOF
23
24gpg --sign --encrypt --recipient $MY_FPR \
25 --output ~/.mau/$MY_FPR/hello.json.pgp \
26 /tmp/hello-v2.json
Your directory now has:
~/.mau/5D000B2F.../
hello.json.pgp # Latest version
hello.json.pgp.versions/
abc123...def.pgp # Old version (SHA-256 hash)
For others to follow you, they need your public key:
1# Export in binary format (Mau standard)
2gpg --export $MY_FPR > /tmp/alice-pubkey.pgp
3
4# Or ASCII-armored (human-readable)
5gpg --export --armor $MY_FPR > /tmp/alice-pubkey.asc
Send this file to friends via email, USB drive, QR code, etc.
Bob imports your key:
1# Bob receives alice-pubkey.pgp and imports it
2gpg --import /tmp/alice-pubkey.pgp
3
4# Encrypt it with his own key and save to .mau directory
5gpg --export $MY_FPR | \
6 gpg --encrypt --recipient $BOB_FPR \
7 --output ~/.mau/.mau/$MY_FPR.pgp
Why encrypt the friend’s public key? To prevent malicious programs from adding fake friends.
✅ PGP identity = public/private key pair
✅ Fingerprint = unique 160-bit identifier
✅ Signing = proves authenticity
✅ Encryption = ensures privacy
✅ Multiple recipients = group communication
✅ File structure = simple directory layout
✅ Versioning = hash-based old versions
✅ Mau addressing = /p2p/<fingerprint>/<filename>
.pgp filesWhat you’ve done manually is tedious:
This is where Mau CLI and Mau packages come in!
mau command for practical workflowsNote: The commands above work on Linux/macOS. Windows users should use WSL or adapt paths accordingly.