Auditable · Open Source · AGPL-3.0

Security and cryptography

We don't ask you to take our word for it. Noctcom is built so that, even if we wanted to, we can't read your files: your keys are born and stay on your device, and the server only ever receives ciphertext it has no way to open. Here we explain exactly how, no spin. And if anything on this page doesn't match the code, open an issue — in the end the code rules, not the promises.

Cryptographic primitives

FunctionAlgorithmParameters
KDFArgon2idOPSLIMIT_MODERATE, 256 MiB
AEADXChaCha20-Poly1305nonce 24B, tag 16B
HKDFBLAKE2b-keyedoutput 32B
SignaturesEd25519RFC 8032
Key ExchangeX25519RFC 7748
Sealed BoxesX25519 + XSalsa20-Poly1305derived nonce
HashBLAKE2b-256output 32B

Key hierarchy

// Full derivation from the password

password

│ Argon2id(salt, opsLimit, memLimit)

Master Key (MK)

├── BLAKE2b("vault.wrap") → K_vault_wrap

│ └── Unwrap → vault_key → file_key → chunks

├── BLAKE2b("login.sign") → seed → Ed25519 keypair

└── Unwrap → sk_exchange (X25519)

// Parallel branch: the recovery phrase (12 BIP39 words)

mnemonic

│ BLAKE2b(key="recovery.v1")

recovery seed

├── Ed25519 → signs the recovery challenge

└── BLAKE2b("recovery.box.v1") → X25519 → opens the kit's seals

Account recovery (kit v2)

Your 12-word phrase (BIP39, 128 bits of entropy) doesn't just recover access: it also recovers your files. From it a X25519 pair is derived whose public key is registered on the server; with that public key, your browser seals (crypto_box_seal) each vault's key and your exchange key. Sealing only needs the public key — opening the seals needs only the private key, which exists solely if you have the phrase.

You forgot the password → nothing is lost

The phrase signs a challenge, the server hands over the seals, your browser opens them and re-encrypts the vault keys with your new password. Your files and everything shared with you stay with you.

The server only keeps sealed envelopes

The seals are ciphertext: neither Noctcom nor an attacker with the database can open them. The private key that opens them is derived from your phrase and never travels.

You lose phrase AND password → unrecoverable

There's no back door, no 'contact support'. It's the price of real zero-knowledge, and we say it with no fine print.

File encryption

4 MiB chunks

Each file is split into 4 MiB chunks, encrypted independently with XChaCha20-Poly1305. Each chunk uses a random 24-byte nonce.

Anti-reorder AAD

Each chunk includes its index as Additional Authenticated Data (AAD = 'chunk:N'). It prevents an attacker from reordering chunks.

Content hash

BLAKE2b-256 over all encrypted chunks. Verifies integrity on every download.

Zero-knowledge email

// The server never sees your email

email_hash = BLAKE2b(

message = normalize(email),

key = "noctcom.email.v1",

output = 32 bytes

)

External audits

No external audits have been carried out yet. Once they are completed, the full reports will be published here and in the repository.

In the meantime, the code is 100% auditable on GitHub.

Audit the code yourself

All the code is public under AGPL-3.0. The reference implementations are in backend/src/crypto/ and frontend/lib/crypto.ts