Skip to content

Encryption

End-to-end encryption with forward secrecy.

Cryptographic Primitives

Harbor uses modern, well-audited cryptographic primitives:

PurposeAlgorithmNotes
Key ExchangeX25519Elliptic curve Diffie-Hellman
SignaturesEd25519EdDSA on Curve25519
EncryptionXChaCha20-Poly1305AEAD with 192-bit nonces
Key DerivationHKDF-SHA256RFC 5869
HashingSHA-256General 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:

  1. Sender generates an ephemeral X25519 key pair
  2. Key agreement between:
    • Sender's ephemeral private key + Recipient's identity public key
    • Sender's ephemeral private key + Recipient's prekey
  3. 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:

  1. Generate new DH key pair
  2. Compute shared secret with recipient's current DH public key
  3. 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:

  1. Derive message key from chain key
  2. Advance chain key using KDF
  3. 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

PropertyProvided By
ConfidentialityXChaCha20 encryption
IntegrityPoly1305 authentication
Forward SecrecyDouble Ratchet DH ratchet
Replay ProtectionOn-chain nullifiers

Learn More