Encryption
End-to-end encryption with forward secrecy.
Cryptographic Primitives
Harbor uses modern, well-audited cryptographic primitives:
| Purpose | Algorithm | Notes |
|---|---|---|
| Key Exchange | X25519 | Elliptic curve Diffie-Hellman |
| Signatures | Ed25519 | EdDSA on Curve25519 |
| Encryption | XChaCha20-Poly1305 | AEAD with 192-bit nonces |
| Key Derivation | HKDF-SHA256 | RFC 5869 |
| Hashing | SHA-256 | General purpose hashing |
XChaCha20-Poly1305
All message encryption uses XChaCha20-Poly1305, an authenticated encryption with associated data (AEAD) cipher:
- XChaCha20 — Stream cipher for encryption
- Poly1305 — MAC for authentication
- 192-bit nonces — Safe for random nonce generation
XChaCha20-Poly1305 was chosen over AES-GCM because:
- Longer nonces eliminate nonce collision concerns
- No timing attacks on software implementations
- Simpler implementation reduces attack surface
Key Exchange
Initial key exchange uses an X3DH-style protocol:
- Sender generates an ephemeral X25519 key pair
- Key agreement between:
- Sender's ephemeral private key + Recipient's identity public key
- Sender's ephemeral private key + Recipient's prekey
- Key derivation using HKDF to produce the initial session key
This provides:
- Authentication — Recipient can verify sender's identity
- Forward secrecy — Ephemeral keys are discarded after use
- Deniability — No proof sender created the message
Double Ratchet
After initial key exchange, Harbor uses the Double Ratchet protocol for ongoing message encryption. The Double Ratchet combines:
Diffie-Hellman Ratchet
Each party maintains a DH key pair. When sending:
- Generate new DH key pair
- Compute shared secret with recipient's current DH public key
- Derive new root key from shared secret
This provides forward secrecy — compromising a DH key doesn't reveal past messages.
Symmetric Ratchet
For each message in one direction:
- Derive message key from chain key
- Advance chain key using KDF
- Use message key to encrypt
This ensures each message uses a unique key, even if sent in rapid succession.
Message Encryption
Each message is encrypted as follows:
// Simplified message encryption const messageKey = deriveMessageKey(chainKey); const nonce = randomBytes(24); // 192-bit random nonce const ciphertext = xchacha20poly1305.seal( messageKey, nonce, plaintext, associatedData );
Associated Data
The associated data (authenticated but not encrypted) includes:
- Sender's current DH public key
- Message sequence number
- Protocol version
Message Padding
Messages are padded to fixed bucket sizes to prevent length-based analysis:
- Padding uses ISO/IEC 7816-4 scheme
- Buckets: 256B, 1KB, 4KB, 16KB
- Message is padded to next bucket size
This prevents observers from inferring message content length from the encrypted payload size.
Nonce Generation
XChaCha20-Poly1305 uses 192-bit (24-byte) nonces, generated using cryptographically secure random number generation:
const nonce = crypto.getRandomValues(new Uint8Array(24));
With 192-bit nonces, the probability of collision is negligible even with billions of messages.
Security Properties
| Property | Provided By |
|---|---|
| Confidentiality | XChaCha20 encryption |
| Integrity | Poly1305 authentication |
| Forward Secrecy | Double Ratchet DH ratchet |
| Replay Protection | On-chain nullifiers |
Learn More
- Identity Model — Key management
- Threat Model — What this protects against
- Security — Security overview