Fula API

πŸ”’ Security & Encryption

In-Depth Technical Documentation of Fula's Cryptographic Implementation

πŸ›‘οΈ Quantum-Safe πŸ” HPKE (RFC 9180) ⚑ AES-256-GCM πŸ”‘ X25519 + ML-KEM-768 βœ… BLAKE3

Security Overview

Fula implements a Trust-No-One (TNO) security model where all encryption happens client-side. Storage nodes and the gateway never see your plaintext data or encryption keys.

πŸ”

Client-Side Encryption

All encryption/decryption happens on your device before data leaves

πŸ”‘

User-Held Keys

Only you possess the private keys needed to decrypt your data

βœ…

Verified Integrity

Cryptographic proofs detect any tampering, even from malicious nodes

πŸ”„

Forward Secrecy

Ephemeral keys ensure past communications remain secure

Cryptographic Primitives Stack

Layer Algorithm Purpose Security Level
πŸ›‘οΈ Quantum-Safe KEM X25519 + ML-KEM-768 Hybrid key encapsulation (classical + post-quantum) NIST Level 3 + 128-bit classical
Key Exchange X25519 Classical Elliptic Curve Diffie-Hellman 128-bit classical
Post-Quantum KEM ML-KEM-768 (Kyber) NIST FIPS 203 lattice-based KEM 192-bit post-quantum (NIST Level 3)
Key Derivation HKDF-SHA256 Combine X25519 + ML-KEM shared secrets 256-bit output
Symmetric Encryption AES-256-GCM Authenticated encryption of data 256-bit (quantum-resistant)
Alternative Cipher ChaCha20-Poly1305 Authenticated encryption (software-optimized) 256-bit (quantum-resistant)
Hashing BLAKE3 Content addressing, integrity verification 256-bit (quantum-resistant)
Streaming Verification Bao Incremental integrity verification Merkle tree based
πŸ›‘οΈ Post-Quantum Security: The hybrid X25519 + ML-KEM-768 approach provides defense-in-depth. If quantum computers break X25519, ML-KEM-768 still protects your data. If ML-KEM has unforeseen weaknesses, X25519 provides classical security. ML-KEM-768 is NIST FIPS 203 standardized (formerly Kyber768).

Trust Model

🟒 Trusted Zone (Your Device)
Private Keys Never leave your device
Plaintext Data Only exists here
Encryption/Decryption Performed locally
Encrypted Data Only β†’
πŸ”΄ Untrusted Zone (Network)
Fula Gateway Sees only ciphertext
IPFS Nodes Store encrypted blocks
Network Transport All data is encrypted

Security Assumptions

  • Your device is secure - If compromised, keys can be extracted
  • Cryptographic primitives are secure - X25519, AES-256-GCM, BLAKE3 are industry-standard
  • Random number generator is secure - Using OS-provided CSPRNG (OsRng)

API Authentication

Fula supports both Bearer tokens and AWS Signature V4, enabling full S3 SDK compatibility.

Authentication Methods

πŸ”‘ Bearer Token (Simple)

For HTTP clients, REST APIs, and custom integrations.

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

πŸ“¦ AWS Signature V4 (S3 Compatible)

For boto3, AWS CLI, aws-sdk-js, and all S3 tools.

Authorization: AWS4-HMAC-SHA256 
  Credential=JWT:eyJhbGci.../20231207/us-east-1/s3/aws4_request,
  SignedHeaders=host;x-amz-date,
  Signature=abc123...

AWS Sig V4 with JWT

Embed your JWT token in the AWS access key with a JWT: prefix. This enables full compatibility with standard S3 clients while maintaining JWT-based authentication.

# Python (boto3)
import boto3

s3 = boto3.client('s3',
    endpoint_url='https://gateway.example.com',
    aws_access_key_id=f'JWT:{jwt_token}',  # JWT embedded here
    aws_secret_access_key='not-used',       # Not validated
    region_name='us-east-1'
)

s3.put_object(Bucket='my-bucket', Key='file.txt', Body=b'Hello!')
# AWS CLI (~/.aws/credentials)
[fula]
aws_access_key_id = JWT:eyJhbGciOiJIUzI1NiIs...
aws_secret_access_key = not-used

# Usage:
aws s3 cp file.txt s3://my-bucket/ --endpoint-url https://gateway.example.com --profile fula
// JavaScript (AWS SDK v3)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://gateway.example.com",
  region: "us-east-1",
  forcePathStyle: true,
  credentials: {
    accessKeyId: `JWT:${jwtToken}`,
    secretAccessKey: "not-used"
  }
});

await s3.send(new PutObjectCommand({ Bucket: "my-bucket", Key: "file.txt", Body: "Hello!" }));

Security Properties

  • JWT Validation: Full signature, expiry, and claims validation
  • Replay Protection: x-amz-date must be within 15 minutes
  • Scope Enforcement: JWT scopes control read/write/delete permissions
  • S3 Compatibility: Works with boto3, AWS CLI, aws-sdk-js, and all S3 tools

Encryption Flow

Complete data flow from plaintext to encrypted storage and back.

πŸ“€ Upload (Encryption) Flow

1 Generate DEK

A fresh 256-bit Data Encryption Key (DEK) is generated using CSPRNG for each object.

DEK = random_bytes(32)  // 256 bits from OsRng
2 Encrypt Data with DEK

Plaintext is encrypted using AES-256-GCM with a random 96-bit nonce.

nonce = random_bytes(12)  // 96 bits
ciphertext = AES-256-GCM.encrypt(DEK, nonce, plaintext)
// ciphertext includes 128-bit authentication tag
3 Wrap DEK with HPKE

The DEK is encrypted using HPKE for the owner's public key.

ephemeral_secret = X25519.generate()
ephemeral_public = X25519.public_key(ephemeral_secret)
shared_secret = X25519.dh(ephemeral_secret, recipient_public)
wrap_key = BLAKE3.derive_key("fula-hpke-v1", shared_secret)
wrapped_DEK = AES-256-GCM.encrypt(wrap_key, nonce2, DEK)
4 Store with Metadata

Ciphertext is uploaded with encryption metadata (nonce, wrapped DEK, ephemeral public key).

metadata = {
  "x-fula-encrypted": "true",
  "x-fula-encryption": {
    "version": 1,
    "algorithm": "AES-256-GCM",
    "nonce": base64(nonce),
    "wrapped_key": {
      "ephemeral_public": base64(ephemeral_public),
      "ciphertext": base64(wrapped_DEK)
    }
  }
}

πŸ“₯ Download (Decryption) Flow

1 Fetch Encrypted Data

Retrieve ciphertext and encryption metadata from the gateway.

2 Unwrap DEK

Use your secret key to derive shared secret and decrypt the DEK.

shared_secret = X25519.dh(your_secret_key, ephemeral_public)
wrap_key = BLAKE3.derive_key("fula-hpke-v1", shared_secret)
DEK = AES-256-GCM.decrypt(wrap_key, nonce2, wrapped_DEK)
3 Decrypt Data

Use the recovered DEK to decrypt the ciphertext.

plaintext = AES-256-GCM.decrypt(DEK, nonce, ciphertext)
// Authentication tag verified automatically

Key Architecture (KEK/DEK)

Fula uses a two-tier key hierarchy for efficient and secure encryption.

πŸ”‘ KEK (Key Encryption Key)
Type: X25519 Key Pair (Asymmetric)
Size: 256-bit (32 bytes)
Lifetime: Long-lived (until rotation)
Purpose: Encrypt/wrap DEKs, enable sharing
Secret Key Kept private, never transmitted
Public Key Shared freely for receiving encrypted data
Wraps ↓
πŸ—οΈ DEK (Data Encryption Key)
Type: AES-256 Key (Symmetric)
Size: 256-bit (32 bytes)
Lifetime: Per-object (unique for each file)
Purpose: Encrypt actual file content
Encrypts ↓
πŸ“„ Your Data

Files, photos, documents, any content you store

Benefits of KEK/DEK Architecture

πŸ”„ Efficient Key Rotation

Rotate KEK without re-encrypting all data - just re-wrap existing DEKs

🀝 Secure Sharing

Share access by wrapping DEK with recipient's public key

⚑ Performance

Symmetric DEKs are fast; asymmetric KEK only used for small DEKs

πŸ”’ Key Isolation

Compromise of one DEK doesn't affect other files

HPKE Implementation

Hybrid Public Key Encryption following RFC 9180 for secure key encapsulation.

HPKE Configuration

KEM (Key Encapsulation) DHKEM(X25519, HKDF-SHA256)
KDF (Key Derivation) BLAKE3 with context "fula-hpke-v1"
AEAD (Encryption) AES-256-GCM (default) or ChaCha20-Poly1305

HPKE Encryption Process

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         SENDER (Encryptor)                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Ephemeral   β”‚         β”‚        Recipient's Public Key         β”‚  β”‚
β”‚  β”‚ Secret Key  β”‚         β”‚         (known to sender)             β”‚  β”‚
β”‚  β”‚ (random)    β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                          β”‚                        β”‚
β”‚         β”‚                                 β”‚                        β”‚
β”‚         β–Ό                                 β–Ό                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚              X25519 Diffie-Hellman Key Exchange               β”‚  β”‚
β”‚  β”‚     shared_secret = DH(ephemeral_secret, recipient_public)    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                 β”‚                                   β”‚
β”‚                                 β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚               BLAKE3 Key Derivation Function                  β”‚  β”‚
β”‚  β”‚        encryption_key = derive_key("fula-hpke-v1",            β”‚  β”‚
β”‚  β”‚                                    shared_secret)             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                 β”‚                                   β”‚
β”‚                                 β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                  AES-256-GCM Encryption                       β”‚  β”‚
β”‚  β”‚        ciphertext = encrypt(encryption_key, nonce, DEK)       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                     β”‚
β”‚  OUTPUT: { ephemeral_public_key, nonce, ciphertext }               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       RECIPIENT (Decryptor)                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Recipient's β”‚         β”‚       Ephemeral Public Key            β”‚  β”‚
β”‚  β”‚ Secret Key  β”‚         β”‚       (from ciphertext)               β”‚  β”‚
β”‚  β”‚ (private)   β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                          β”‚                        β”‚
β”‚         β”‚                                 β”‚                        β”‚
β”‚         β–Ό                                 β–Ό                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚              X25519 Diffie-Hellman Key Exchange               β”‚  β”‚
β”‚  β”‚     shared_secret = DH(recipient_secret, ephemeral_public)    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                 β”‚                                   β”‚
β”‚                                 β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚           Same shared_secret β†’ Same encryption_key            β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                 β”‚                                   β”‚
β”‚                                 β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                  AES-256-GCM Decryption                       β”‚  β”‚
β”‚  β”‚            DEK = decrypt(encryption_key, nonce,               β”‚  β”‚
β”‚  β”‚                         ciphertext)                           β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security Properties

βœ…
Forward Secrecy

Each encryption uses a fresh ephemeral key pair. Compromise of long-term keys doesn't decrypt past messages.

βœ…
Recipient Authentication

Only the holder of the recipient's secret key can derive the shared secret and decrypt.

βœ…
Ciphertext Integrity

AES-GCM provides authenticated encryption - any tampering is detected.

Symmetric Encryption (AEAD)

Authenticated Encryption with Associated Data for file content.

Supported Ciphers

Default

AES-256-GCM

  • Key Size: 256 bits (32 bytes)
  • Nonce Size: 96 bits (12 bytes)
  • Tag Size: 128 bits (16 bytes)
  • Best For: Hardware with AES-NI support
Alternative

ChaCha20-Poly1305

  • Key Size: 256 bits (32 bytes)
  • Nonce Size: 96 bits (12 bytes)
  • Tag Size: 128 bits (16 bytes)
  • Best For: Software-only implementations

Ciphertext Structure

Ciphertext Encrypted plaintext (same length as input)
Auth Tag 16 bytes
output_length = plaintext_length + 16 bytes (tag)

Nonce Generation

⚠️ Critical: Nonces must NEVER be reused with the same key.

Fula's Strategy: Random Nonces

  • 96-bit nonces generated from CSPRNG (OsRng)
  • Collision probability: ~2-48 after 248 encryptions
  • Each file gets a unique DEK, so nonce space is per-DEK
  • Safe for virtually unlimited encryptions per file

Data Integrity Verification

Cryptographic guarantees that your data hasn't been tampered with.

Multi-Layer Integrity

1

AEAD Authentication Tag

Every encrypted block includes a 128-bit authentication tag that verifies both ciphertext integrity and the encryption key used.

βœ… Detects: bit flips, truncation, appending
2

Content Addressing (CID)

Every block stored in IPFS has a CID derived from its BLAKE3 hash. Retrieving by CID guarantees content matches.

βœ… Detects: storage corruption, node manipulation
3

Bao Streaming Verification

For large files, Bao provides Merkle tree-based verification allowing you to verify chunks before downloading the entire file.

βœ… Detects: partial corruption in large files

BLAKE3 Hashing

⚑ Speed

Faster than MD5, SHA-1, SHA-256, SHA-3 on modern CPUs

πŸ”’ Security

256-bit security level, resistant to length extension

πŸ”€ Parallelizable

Scales with CPU cores for large data

πŸ”‘ KDF Mode

Built-in key derivation with context separation

Bao Verified Streaming

                        Root Hash (32 bytes)
                              β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                               β”‚
         Hash(L||R)                       Hash(L||R)
              β”‚                               β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
       β”‚             β”‚                 β”‚             β”‚
    Hash(L||R)   Hash(L||R)        Hash(L||R)   Hash(L||R)
       β”‚             β”‚                 β”‚             β”‚
    β”Œβ”€β”€β”΄β”€β”€β”       β”Œβ”€β”€β”΄β”€β”€β”          β”Œβ”€β”€β”΄β”€β”€β”       β”Œβ”€β”€β”΄β”€β”€β”
    β”‚     β”‚       β”‚     β”‚          β”‚     β”‚       β”‚     β”‚
  Chunk Chunk   Chunk Chunk      Chunk Chunk   Chunk Chunk
    1     2       3     4          5     6       7     8

  β€’ Verify any chunk with O(log n) proof from root
  β€’ Detect corruption before downloading entire file
  β€’ Stream large files with incremental verification

Key Management

How Fula handles key generation, storage, rotation, and recovery.

Key Generation

Random Source OsRng (Operating System CSPRNG)
KEK Generation 32 random bytes β†’ X25519 secret key
DEK Generation 32 random bytes β†’ AES-256 key
Nonce Generation 12 random bytes per encryption

Key Rotation

1
Generate New KEK

Create a fresh X25519 key pair

2
Re-wrap DEKs

Decrypt each DEK with old KEK, re-encrypt with new KEK

3
Update Metadata

Store new wrapped DEKs, increment version counter

4
Secure Delete Old KEK

Zeroize old secret key from memory

Note: Data does NOT need to be re-encrypted - only the DEK wrappers change.

Key Backup & Recovery

⚠️
Critical: If you lose your secret key, your data is UNRECOVERABLE.

Fula uses true end-to-end encryption. There are no backdoors, no "forgot password" recovery, no master keys.

Recommended Backup Strategies

  • Export as Base64: secret_key.to_base64() β†’ store in password manager
  • Hardware Security Module: Store key in HSM or secure enclave
  • Paper Backup: Print Base64 key, store in secure physical location
  • Split Key: Use Shamir's Secret Sharing for distributed backup

Memory Security

All key types implement Zeroize and ZeroizeOnDrop:

  • Keys are automatically zeroed when dropped from memory
  • Prevents keys from lingering in memory after use
  • Protects against memory dump attacks
// From fula-crypto/src/keys.rs
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct SecretKey {
    bytes: [u8; 32],
}
// When SecretKey is dropped, bytes are overwritten with zeros

Secure File & Folder Sharing

Share files and folders with others without exposing your master key.

Async/Offline Inbox Sharing (WNFS-Inspired)

Store-and-forward sharing where recipients can pick up shares later without the sharer being online.

How It Works

Inspired by WNFS's exchange directories, Fula supports asynchronous sharing where:

  • Sharer creates an encrypted ShareEnvelope and stores it in the recipient's inbox
  • Recipient can later list, decrypt, and accept shares when convenient
  • No coordination required - both parties can be offline at different times
Sharer                          Storage                         Recipient
  β”‚                                β”‚                                β”‚
  β”‚ 1. Create ShareEnvelope        β”‚                                β”‚
  β”‚    + HPKE encrypt for recipientβ”‚                                β”‚
  β”‚                                β”‚                                β”‚
  β”‚ 2. Store in inbox ─────────────│►[Encrypted entry stored]       β”‚
  β”‚                                β”‚                                β”‚
  β”‚                                β”‚  [Later, recipient comes online]
  β”‚                                β”‚                                β”‚
  β”‚                                │◄──3. List inbox entries ───────│
  β”‚                                β”‚                                β”‚
  β”‚                                │◄──4. Decrypt & accept ─────────│
  β”‚                                β”‚                                β”‚
  β”‚                                β”‚  5. Use ShareToken to access   β”‚
  β”‚                                β”‚     shared content             β”‚
use fula_crypto::{ShareEnvelopeBuilder, ShareInbox, KekKeyPair, DekKey};

// === SHARER FLOW ===
let sharer = KekKeyPair::generate();
let recipient = KekKeyPair::generate();
let dek = DekKey::generate();

// Create share envelope with metadata
let (envelope, entry) = ShareEnvelopeBuilder::new(
    &sharer,
    recipient.public_key(),
    &dek
)
    .path_scope("/photos/vacation/")
    .expires_in(7 * 24 * 3600)  // 1 week
    .read_only()
    .label("Vacation Photos 2024")
    .message("Check out these pics from Hawaii!")
    .sharer_name("Alice")
    .build()?;

// Store entry in recipient's inbox (via PrivateForest)
let inbox_path = ShareInbox::entry_storage_path(recipient.public_key(), &entry.id);
// put_object_flat(bucket, &inbox_path, serialize(&entry), ...);

// === RECIPIENT FLOW ===
let mut inbox = ShareInbox::new();

// Load entries from storage
inbox.add_entry(entry);

// List pending shares
let pending = inbox.list_pending(&recipient);
println!("You have {} new shares", pending.len());

// Accept a share
let accepted = inbox.accept_entry(&entry_id, &recipient)?;
println!("From: {:?}", accepted.sharer_name);  // "Alice"
println!("Message: {:?}", accepted.message);   // "Check out these pics..."

// Now use the ShareToken to access content
let dek = recipient.accept_share(&accepted.token)?;

Key Features

πŸ“¬ Offline Delivery

Shares are stored encrypted until recipient comes online

πŸ”’ End-to-End Encrypted

Only recipient can decrypt inbox entries using HPKE

πŸ“ Rich Metadata

Include labels, messages, and sharer info with each share

⏰ Auto-Expiry

Configurable TTL for inbox entries (default: 30 days)

Subtree Keys (Peergos Cryptree-Inspired)

Allocate separate DEKs for major folders for better revocation and least privilege.

What are Subtree Keys?

Inspired by Peergos Cryptree, Fula supports a shallow key hierarchy where top-level folders can have their own DEKs:

Master DEK (bucket-level)
     β”‚
     β”œβ”€β”€ /photos/ ─── Subtree DEK A ─── [beach.jpg, sunset.jpg, ...]
     β”‚
     β”œβ”€β”€ /documents/ ─── Subtree DEK B ─── [report.pdf, notes.txt, ...]
     β”‚
     └── /apps/myapp/ ─── Subtree DEK C ─── [config.json, data.bin, ...]

Sharing /photos/ only exposes Subtree DEK A.
Revoking that share only requires re-keying /photos/, not the whole bucket.

Key Benefits

πŸ”„ Efficient Revocation

Re-key just one subtree instead of rotating the entire bucket

πŸ”’ Least Privilege

A subtree share cannot be escalated to access unrelated data

⚑ Low Overhead

Still uses a single PrivateForest - no structural changes needed

use fula_crypto::{SubtreeKeyManager, SubtreeShareBuilder, DekKey, KekKeyPair};

// Create manager with master DEK
let master_dek = DekKey::generate();
let mut manager = SubtreeKeyManager::with_master_dek(master_dek);

// Create subtrees with their own DEKs
let (photos_dek, encrypted_photos) = manager.create_subtree("/photos/")?;
let (docs_dek, encrypted_docs) = manager.create_subtree("/documents/")?;

// Resolve DEK for a file path
let dek = manager.resolve_dek("/photos/vacation/beach.jpg");  // Returns photos_dek

// Share a subtree with someone
let owner = KekKeyPair::generate();
let recipient = KekKeyPair::generate();

let share = SubtreeShareBuilder::new(
    &owner,
    recipient.public_key(),
    &photos_dek,
    "/photos/",
    1,  // version
)
    .expires_in(86400)  // 24 hours
    .read_only()
    .build()?;

// Rotate subtree key after revocation
let rotation = manager.rotate_subtree("/photos/")?;
// rotation.new_dek is now used for /photos/*
// Old shares become invalid

When to Use Subtree Keys

Scenario Recommendation
Sharing app-specific data folders Use subtree DEK per app namespace
Sharing project folders with different teams Use subtree DEK per project
Need to revoke access to specific shared folder Rotate just that subtree's DEK
Single user, no sharing Master DEK is sufficient

Full Filesystem Key Rotation

Rotate your KEK without re-encrypting file content.

Why Rotate Keys?

  • Suspected Compromise: If you think your key might be exposed
  • Regular Security Hygiene: Periodic rotation limits exposure window
  • Personnel Changes: When someone with key access leaves
  • Compliance Requirements: Some regulations mandate key rotation

Rotation Process

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      KEY ROTATION FLOW                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                  β”‚
β”‚  BEFORE ROTATION:                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     wraps      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚  β”‚  KEK v1     β”‚ ──────────────→│   DEK A    │──→ File A        β”‚
β”‚  β”‚ (current)   β”‚                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚  β”‚             β”‚     wraps      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚  β”‚             β”‚ ──────────────→│   DEK B    │──→ File B        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚                                                                  β”‚
β”‚  ROTATION STEP 1: Generate new KEK                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”‚
β”‚  β”‚  KEK v1     β”‚                β”‚  KEK v2     β”‚                 β”‚
β”‚  β”‚ (previous)  β”‚                β”‚ (current)   β”‚                 β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚
β”‚                                                                  β”‚
β”‚  ROTATION STEP 2: Re-wrap each DEK                               β”‚
β”‚                                                                  β”‚
β”‚  For each file:                                                  β”‚
β”‚    1. Decrypt DEK with KEK v1                                    β”‚
β”‚    2. Re-encrypt DEK with KEK v2                                 β”‚
β”‚    3. Update wrapped DEK in metadata                             β”‚
β”‚                                                                  β”‚
β”‚  AFTER ROTATION:                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     wraps      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚  β”‚  KEK v2     β”‚ ──────────────→│   DEK A    │──→ File A        β”‚
β”‚  β”‚ (current)   β”‚                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   (unchanged)    β”‚
β”‚  β”‚             β”‚     wraps      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚  β”‚             β”‚ ──────────────→│   DEK B    │──→ File B        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   (unchanged)    β”‚
β”‚                                                                  β”‚
β”‚  Note: File content is NEVER re-encrypted!                       β”‚
β”‚        Only the DEK wrappers are updated.                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Example

use fula_crypto::{KekKeyPair, DekKey, FileSystemRotation};

// Initialize with current keypair
let keypair = KekKeyPair::generate();
let mut fs = FileSystemRotation::new(keypair)
    .with_batch_size(100);  // Process 100 files per batch

// Register existing files
for file in files {
    fs.wrap_new_file(&file.path, &file.dek)?;
}

// Initiate rotation (generates new KEK)
let new_public_key = fs.rotate();

// Rotate in batches (for large filesystems)
while !fs.is_rotation_complete() {
    let result = fs.rotate_batch();
    println!("Rotated {} files", result.rotated_count);
}

// All files now use KEK v2
// Old KEK is automatically cleared

Key Benefits

⚑ Efficient

Only re-wraps DEKs (32 bytes each), never re-encrypts file content

πŸ“¦ Incremental

Process files in batches to avoid overwhelming large systems

πŸ”„ Backward Compatible

Old wrapped keys still work during transition period

βœ… Verified

Track rotation progress and verify completion

Metadata Privacy

Protect not just your data, but also your file names, sizes, and timestamps.

Why Metadata Privacy Matters

Even with encrypted content, metadata can reveal sensitive information:

  • File names like "tax_returns_2024.pdf" or "medical_records.docx" reveal intent
  • File sizes can identify document types or media content
  • Timestamps show when you accessed or modified files
  • Content types indicate what kind of data you're storing

How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   METADATA PRIVACY FLOW                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                  β”‚
β”‚  CLIENT SIDE (Original Metadata):                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Key:        /finances/investment_portfolio_2024.xlsx       β”‚ β”‚
β”‚  β”‚ Size:       156,789 bytes                                  β”‚ β”‚
β”‚  β”‚ Type:       application/vnd.openxmlformats...              β”‚ β”‚
β”‚  β”‚ Modified:   2024-12-07T15:30:00Z                           β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                           β”‚                                      β”‚
β”‚                           β–Ό                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚              METADATA ENCRYPTION                           β”‚ β”‚
β”‚  β”‚  1. Create PrivateMetadata struct with original values    β”‚ β”‚
β”‚  β”‚  2. Encrypt with file's DEK using AES-256-GCM             β”‚ β”‚
β”‚  β”‚  3. Generate obfuscated storage key via BLAKE3            β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                           β”‚                                      β”‚
β”‚                           β–Ό                                      β”‚
β”‚  SERVER SIDE (What storage nodes see):                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Key:        e/a7c3f9b2e8d14a6f (obfuscated hash)          β”‚ β”‚
β”‚  β”‚ Size:       156,821 bytes (ciphertext size)               β”‚ β”‚
β”‚  β”‚ Type:       application/octet-stream (generic)            β”‚ β”‚
β”‚  β”‚ Metadata:   [encrypted blob]                              β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Obfuscation Modes

DeterministicHash

Same file path β†’ Same storage key. Allows retrieval without local index.

/photos/beach.jpg β†’ e/a7c3f9b2e8d14a6f

RandomUuid

Each upload gets a random key. Maximum privacy but requires local mapping.

/photos/beach.jpg β†’ e/random-uuid-here

PreserveStructure

Keep directory paths, hash only filenames. Allows folder browsing.

/photos/beach.jpg β†’ /photos/e_a7c3f9b2

Code Example

use fula_client::{EncryptedClient, EncryptionConfig, KeyObfuscation};

// DEFAULT: FlatNamespace for complete structure hiding
let encryption = EncryptionConfig::new();  // FlatNamespace by default!
let client = EncryptedClient::new(config, encryption)?;

// Upload - server sees: QmX7a8f3e2d1c9b4a5e6f7d8...
client.put_object_flat(bucket, "/secret/file.txt", data, None).await?;

// List files from encrypted PrivateForest index
let files = client.list_files_from_forest(bucket).await?;

// Server sees NOTHING about your folder structure!

// ─────────────────────────────────────────────────────────
// Alternative modes (for specific use cases):
// ─────────────────────────────────────────────────────────

// DeterministicHash - same path = same key, no local index needed
let encryption = EncryptionConfig::new()
    .with_obfuscation_mode(KeyObfuscation::DeterministicHash);

// PreserveStructure - keeps folder paths visible
let encryption = EncryptionConfig::new()
    .with_obfuscation_mode(KeyObfuscation::PreserveStructure);

// Disable metadata privacy entirely (not recommended)
let encryption = EncryptionConfig::new_without_privacy();

// Get full metadata including original filename
let info = client.get_object_with_private_metadata(bucket, storage_key).await?;
println!("Original name: {}", info.original_key);
println!("Original size: {}", info.original_size);

Security Guarantees

What the current implementation provides and its limitations.

βœ… Provided

  • Confidentiality: Data encrypted with AES-256-GCM; only key holder can decrypt
  • Integrity: AEAD tags detect any tampering with ciphertext
  • Authenticity: Only holder of secret key can produce valid decryption
  • Forward Secrecy: Ephemeral keys in HPKE protect past data
  • Key Isolation: Each file has unique DEK; compromise affects only that file
  • Semantic Security: Same plaintext encrypts to different ciphertext each time
  • Ciphertext Indistinguishability: Encrypted data appears random
  • Metadata Privacy: File names, sizes, timestamps encrypted (optional, enabled by default)

❌ Not Provided

  • Sender Authentication: Recipients can't verify who encrypted the data
  • Deniability: Encryption proves you had the key
  • Traffic Analysis Resistance: Access patterns may be observable
  • Post-Quantum Security: X25519 vulnerable to quantum computers

Threat Model

What attacks Fula protects against and what it doesn't.

πŸ›‘οΈ Protected Against

Malicious Storage Nodes

Nodes only see encrypted data. They cannot read your files or forge valid data.

Network Eavesdroppers

All data in transit is encrypted. Even if intercepted, it's unreadable.

Data Tampering

AEAD tags and content addressing detect any modifications.

Gateway Compromise

Gateway never sees plaintext or keys. Compromise only affects availability, not confidentiality.

Replay Attacks

Unique nonces prevent replaying old ciphertexts.

⚠️ Not Protected Against

Compromised Client Device

If your device is compromised, attacker can extract keys.

Rubber Hose Cryptanalysis

Coercion/legal compulsion to reveal keys.

Side-Channel Attacks

Timing, power analysis on the local device.

Quantum Computers

X25519 can be broken by sufficiently powerful quantum computers.

Multi-Device Key Management

Securely manage encryption keys across multiple devices.

Key Management Patterns

Fula supports multiple patterns for managing keys across devices:

Pattern A: Shared Identity

All devices share the same master key. Simple but all devices are equally trusted.

// All devices use same backed-up secret
let master_secret = SecretKey::from_bytes(backed_up_secret)?;
let keypair = KekKeyPair::from_secret(master_secret);
let key_manager = KeyManager::new(&keypair);
// Files encrypted on one device readable on all
+ Simple setup + Seamless sync - No device revocation

Handling Device Loss

When a device is lost or stolen:

  1. Immediate: Rotate KEK on remaining devices
  2. Re-wrap: Use FileSystemRotation to re-wrap all DEKs
  3. Revoke: Remove device from any shared access
  4. Audit: Review what data the device had access to
// On remaining device - rotate immediately
let mut rotation = FileSystemRotation::new(current_keypair);
let new_public_key = rotation.rotate();

// Re-wrap all DEKs with new KEK
while !rotation.is_rotation_complete() {
    rotation.rotate_batch();
}
// Lost device's wrapped keys are now useless

Key Backup Strategy

πŸ“ Paper Backup

24-word mnemonic phrase stored offline in multiple secure locations

πŸ” Hardware Security

YubiKey, Ledger, or other HSM for master key storage

☁️ Encrypted Cloud

Password-protected backup on different provider than data

Recovery Scenarios

Scenario Recovery Method Data Loss
Lost device (backup exists) Restore from backup None
Lost device (no backup) Cannot recover All data
Compromised device (detected early) Rotate + restore None
Compromised device (delayed detection) Rotate + audit Potentially exposed

Security Best Practices

Recommendations for securely using Fula encryption.

πŸ”‘

Key Management

  • Generate keys on a secure, trusted device
  • Back up your secret key in multiple secure locations
  • Never share or transmit your secret key
  • Rotate keys periodically or after potential compromise
πŸ’»

Client Security

  • Keep your device and OS updated
  • Use full-disk encryption
  • Use a hardware security module if available
  • Clear keys from memory after use
πŸ”’

Data Handling

  • Always enable encryption for sensitive data
  • Verify file integrity after download
  • Don't store sensitive metadata in unencrypted fields
  • Use unique DEKs per file (default behavior)
πŸ”„

Operational Security

  • Monitor for unauthorized access attempts
  • Have an incident response plan for key compromise
  • Test key recovery procedures periodically
  • Audit encryption settings regularly