You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Your Device Server
─────────── ──────
"Meeting with
Alice at 3pm"
│
▼
┌─────────────┐
│ Encrypt │ Your encryption
│ with YOUR │ key never leaves
│ key │ your device!
└─────────────┘
│
▼
"xK8j2mN9pL..." ──────────────────► "xK8j2mN9pL..."
(encrypted) (still encrypted)
Server can only see:
• Encrypted blob
• Size of blob
• When it was saved
Server CANNOT see:
• "Meeting with Alice"
• "3pm"
• Anything inside
What HTTPS Protects
Protected by HTTPS (NOT protected without it)
🔴 Auth token in headers → Anyone on network can steal your session
🔴 API endpoints you call → Attacker knows you're syncing calendars
🔴 Request/response sizes → Can infer activity patterns
🔴 Your IP address → Identifies you (though server sees this anyway)
What an Attacker on Your Network Could Do WITHOUT HTTPS
-- User info (from GoatSync database)
django_userinfo:
owner_id: 1
login_pubkey: <Ed25519 PUBLIC key - for signature verification>
pubkey: <Your PUBLIC key - for sharing>
encrypted_content: <Your ENCRYPTED account data>
salt: <For key derivation>-- Collection (encrypted!)
django_collection:
uid: "abc123..."-- Server CANNOT read content!-- Items (encrypted!)
django_collectionitem:
uid: "xyz789..."
encryption_key: <ENCRYPTED collection key>-- Server CANNOT read content!-- Revisions (encrypted!)
django_collectionitemrevision:
meta: <ENCRYPTED metadata>-- Server CANNOT read content!
Code Walkthrough
1. Key Derivation (internal/crypto/etebase.go)
// GetEncryptionKey derives an encryption key using BLAKE2b// This is called during login/signup to derive the server-side encryption keyfunc (e*Etebase) GetEncryptionKey(salt []byte) ([32]byte, error) {
// Step 1: Hash the server's secret key// This creates a key from the server's ENCRYPTION_SECRET env variablekey:=blake2b.Sum256([]byte(e.secretKey))
// Step 2: Use BLAKE2b with personalization// The "etebase-auth" personalization ensures this key is only for authh, err:=blake2b.New(&blake2b.Config{
Size: 32, // 256-bit outputKey: key[:], // Keyed hashSalt: salt[:16], // First 16 bytes of saltPerson: []byte("etebase-auth"), // Personalization string
})
// This produces a deterministic key that:// - Is unique per user (different salt)// - Is tied to this server (ENCRYPTION_SECRET)// - Can only be used for authentication ("etebase-auth")
}
// Encrypt encrypts data using NaCl SecretBox (XSalsa20-Poly1305)func (e*Etebase) Encrypt(key [32]byte, plaintext []byte) []byte {
// Step 1: Generate random nonce (24 bytes)varnonce [24]byterand.Read(nonce[:])
// Step 2: Encrypt with SecretBox// XSalsa20 for encryption + Poly1305 for authenticationciphertext:=secretbox.Seal(nonce[:], plaintext, &nonce, &key)
// Output: nonce (24) + ciphertext (len(plaintext) + 16 for auth tag)returnciphertext
}
// Decrypt decrypts data using NaCl SecretBoxfunc (e*Etebase) Decrypt(key [32]byte, ciphertext []byte) ([]byte, error) {
// Step 1: Extract nonce from beginningvarnonce [24]bytecopy(nonce[:], ciphertext[:24])
// Step 2: Decryptplaintext, ok:=secretbox.Open(nil, ciphertext[24:], &nonce, &key)
if!ok {
returnnil, errors.New("decryption failed")
}
returnplaintext, nil
}
Crypto Algorithms Explained
1. BLAKE2b (Key Derivation)
What: A cryptographic hash function (like SHA-256 but better)
Why: Fast, secure, supports keyed hashing and personalization
Used: Deriving encryption keys from passwords/secrets
BLAKE2b Features Used:
┌─────────────────────────────────────────────────────────────┐
│ BLAKE2b( │
│ message = "", // Can be empty │
│ key = server_secret, // Makes output unique to server │
│ salt = user_salt, // Makes output unique to user │
│ person = "etebase-auth" // Makes output unique to purpose │
│ ) │
│ │
│ Output: 32 bytes of deterministic but unpredictable data │
└─────────────────────────────────────────────────────────────┘
2. Ed25519 (Authentication)
What: Elliptic curve digital signature algorithm
Why: Fast, secure, 64-byte signatures, 32-byte keys
Used: Proving you know your password without sending it
How Login Works:
┌─────────────────────────────────────────────────────────────┐
│ CLIENT SERVER │
│ │
│ 1. Request challenge ──────────────────────► │
│ │
│ 2. ◄────────────── Send encrypted challenge │
│ (encrypted with key derived from password) │
│ │
│ 3. Decrypt challenge (proves you know password) │
│ Sign with Ed25519 PRIVATE key │
│ │
│ 4. Send signature ──────────────────────────► │
│ │
│ 5. Verify with Ed25519 PUBLIC key │
│ (stored during signup) │
│ │
│ Result: Server knows you have the private key │
│ without ever seeing it! │
└─────────────────────────────────────────────────────────────┘
Password is NEVER sent over the network!
CLIENT SERVER
────── ──────
1. User enters: username, email, password
2. Client generates:
• Ed25519 keypair (loginPubkey, loginPrivkey)
• Another keypair for encryption (pubkey, privkey)
• Random salt (16 bytes)
• Encrypts account data with derived key
3. Client sends to server:
┌─────────────────────────────────────────┐
│ { │
│ username: "alice", │
│ email: "alice@example.com", │
│ salt: <16 random bytes>, │
│ loginPubkey: <Ed25519 public key>, │ ◄─ Server stores this
│ pubkey: <Encryption public key>, │ ◄─ For sharing later
│ encryptedContent: <Encrypted data> │ ◄─ Server can't read
│ } │
└─────────────────────────────────────────┘
4. Server stores in database:
• loginPubkey → For verifying login signatures
• pubkey → For member invitations
• encryptedContent → Client's encrypted account data
• salt → Sent back during login challenge
Server NEVER has:
• Password
• Private keys
• Decryption keys
Example 2: Login Flow
CLIENT SERVER
────── ──────
1. "I want to login as alice"
─────────────────────────────────────────────────────►
2. ◄─────────────────────
Server sends:
{
salt: <alice's salt>,
challenge: <encrypted blob>,
version: 1
}
3. Client:
a. Derives key using password + salt
b. Decrypts challenge (if password wrong, fails here!)
c. Signs decrypted challenge with Ed25519 private key
4. Client sends signature
─────────────────────────────────────────────────────►
{
username: "alice",
challenge: <decrypted challenge>,
signature: <Ed25519 signature>
}
5. Server:
a. Gets alice's loginPubkey from database
b. Verifies signature matches
c. Creates auth token
6. ◄─────────────────────
{ token: "abc123..." }
KEY INSIGHT: Password NEVER sent! Signature proves knowledge.
Example 3: Syncing Calendar Event
CLIENT SERVER
────── ──────
1. User creates event: "Meeting with Alice at 3pm"
2. Client encrypts:
┌──────────────────────────────────────────────────────┐
│ Plaintext: │
│ { title: "Meeting with Alice at 3pm", │
│ start: "2024-01-15T15:00:00", │
│ ... } │
│ │
│ ↓ Encrypt with collection key │
│ │
│ Ciphertext: "xK8j2mN9pL3qR7..." (unreadable blob) │
└──────────────────────────────────────────────────────┘
3. Client sends to server:
POST /api/v1/collection/abc123/item/transaction/
Authorization: Token xyz789
{
items: [{
uid: "event123",
content: "xK8j2mN9pL3qR7..." ◄─ Encrypted!
}]
}
4. Server stores encrypted blob
• Cannot read "Meeting with Alice"
• Cannot read "3pm"
• Only sees: "xK8j2mN9pL3qR7..."
5. Later, another device syncs:
GET /api/v1/collection/abc123/item/
Server returns: { content: "xK8j2mN9pL3qR7..." }
Client decrypts with same collection key
→ "Meeting with Alice at 3pm"
HTTP vs HTTPS Summary
What's Safe Without HTTPS (Thanks to E2EE)
Data
Protection
Safe?
Calendar events
E2EE encrypted
✅ Yes
Contact details
E2EE encrypted
✅ Yes
Task content
E2EE encrypted
✅ Yes
Collection names
E2EE encrypted
✅ Yes
Your password
Never sent (Ed25519)
✅ Yes
What's Exposed Without HTTPS
Data
Risk
Mitigation
Auth token
Session hijacking
Use short-lived tokens
API endpoints called
Activity tracking
VPN or HTTPS
Request timing
Pattern analysis
VPN or HTTPS
Your IP address
Location tracking
VPN or Tor
Recommendations
┌─────────────────────────────────────────────────────────────┐
│ DECISION TREE │
│ │
│ Where are you accessing from? │
│ │
│ ┌─────────────────┐ │
│ │ Home network │ ──► HTTP is fine ✅ │
│ │ only? │ Your data is encrypted anyway │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Via VPN │ ──► HTTP is fine ✅ │
│ │ (Tailscale, │ VPN encrypts the transport │
│ │ WireGuard)? │ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Public internet │ ──► Use HTTPS ⚠️ │
│ │ without VPN? │ Or set up Tailscale (free!) │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ Paranoid about │ ──► HTTPS + VPN + Tor 🔒 │
│ │ metadata? │ Maximum protection │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
TL;DR
E2EE works without HTTPS - Your data is encrypted before leaving your device
Password never sent - Ed25519 signature proves you know it
Server is blind - Cannot read your calendars, contacts, or tasks
HTTP risk is auth token - Someone could steal your session
For home use, HTTP is fine - Your data is still encrypted
For public internet, use HTTPS or VPN - Protect your session
GoatSync's E2EE means even if someone hacks the server, they only get encrypted blobs they can't decrypt! 🔒