Backup encryption
QuietGuard\Monitor\Backup\BackupCipher provides the streaming, hybrid-encryption primitives behind the zero-knowledge backup vault. It is framework-agnostic and built entirely on libsodium (ext-sodium). The server only ever stores the opaque result; it never holds the key, so it can never read a backup.
The scheme
A backup is encrypted with a fresh random symmetric key, and that key is then sealed to the team's X25519 public key:
- The archive is encrypted with libsodium's secretstream (XChaCha20-Poly1305) in 64 KiB plaintext chunks (chunked AEAD).
- The random symmetric key is sealed to the team public key with
crypto_box_seal. - Restoring requires the team private key, which is unwrapped locally from the team passphrase: mirroring the server's wrapping (Argon2id MODERATE →
secretbox) so the operator is never in the loop.
The resulting blob layout is:
Encrypting a file
encryptFile() generates a one-time secretstream key, seals it to the base64-encoded public key, writes the sealed key, the stream header and the AEAD chunks (the final chunk carries the TAG_FINAL marker), zeroing the symmetric key from memory when done. It streams the input, so memory use is bounded regardless of archive size.
Decrypting a file
Decryption needs both the public key and the raw private key bytes:
decryptFile() reads the sealed key, recovers the symmetric key with the X25519 key pair, then pulls and verifies each chunk. It throws a RuntimeException if the key pair cannot open the sealed key ("Unable to recover the backup key (wrong key pair)") or if a chunk fails its authentication tag ("Backup is corrupted or has been tampered with").
Unwrapping the private key
The private key needed above is obtained from the server-wrapped key material plus the team passphrase:
unwrapPrivateKey() derives a key-encryption key from the passphrase and salt with Argon2id at the MODERATE ops/mem limits (ALG_ARGON2ID13), then opens the secretbox frame (nonce | ciphertext). It throws a RuntimeException on corrupted material or a wrong passphrase. This intentionally mirrors the server's TeamCipher wrapping so the key can be reconstructed client-side and the server never sees it.
Where this is used
The clients build their monitor:backup / monitor:restore commands on these primitives. The Laravel SDK exposes a thin Support\BackupCipher subclass of this class; the Symfony bundle and WordPress plugin reuse the core directly. The corresponding server-side flow (opaque blob storage, GB quota, key-material API) is part of the Quiet Guard backup vault.
You are reading the PHP Core v1.0 documentation.