Cifrado de las copias de seguridad

QuietGuard\Monitor\Backup\BackupCipher proporciona las primitivas de cifrado híbrido en flujo que sustentan la bóveda de copias de seguridad de conocimiento cero. Es agnóstico respecto al framework y está construido por completo sobre libsodium (ext-sodium). El servidor no almacena más que el resultado opaco; nunca posee la clave, y por tanto nunca puede leer una copia de seguridad.

El esquema

Una copia de seguridad se cifra con una clave simétrica aleatoria nueva, y esa clave se sella después hacia la clave X25519 pública del equipo:

  • El archivo se cifra con el secretstream de libsodium (XChaCha20-Poly1305) en chunks de texto claro de 64 KiB (AEAD por fragmentos).
  • La clave simétrica aleatoria se sella hacia la clave pública del equipo con crypto_box_seal.
  • La restauración requiere la clave privada del equipo, desenvuelta localmente a partir de la passphrase del equipo, reflejando el sellado del servidor (Argon2id MODERATE → secretbox) para que el operador nunca esté en el circuito.

La disposición del blob resultante es:

text
[uint32 sealedKeyLen][sealedKey][header de 24 bytes]([uint32 len][chunk])*

Cifrar un archivo

php
use QuietGuard\Monitor\Backup\BackupCipher;

$cipher = new BackupCipher();

$cipher->encryptFile(
    inPath: '/tmp/backup.tar',
    outPath: '/tmp/backup.tar.enc',
    publicKeyBase64: $teamPublicKeyBase64,
);

encryptFile() genera una clave secretstream de un solo uso, la sella hacia la clave pública codificada en base64, escribe la clave sellada, el header del flujo y los chunks AEAD (el último chunk lleva el marcador TAG_FINAL), borrando la clave simétrica de la memoria al terminar. Procesa la entrada en flujo, de modo que el uso de memoria permanece acotado sea cual sea el tamaño del archivo.

Descifrar un archivo

El descifrado requiere a la vez la clave pública y los bytes en bruto de la clave privada:

php
$cipher->decryptFile(
    inPath: '/tmp/backup.tar.enc',
    outPath: '/tmp/restored.tar',
    publicKeyBase64: $teamPublicKeyBase64,
    privateKeyRaw: $privateKeyRaw,
);

decryptFile() lee la clave sellada, recupera la clave simétrica con el par de claves X25519, y luego extrae y verifica cada chunk. Lanza una RuntimeException si el par de claves no puede abrir la clave sellada ("Unable to recover the backup key (wrong key pair)") o si un chunk falla su verificación de autenticación ("Backup is corrupted or has been tampered with").

Desenvolver la clave privada

La clave privada necesaria más arriba se obtiene a partir del material de clave sellado por el servidor más la passphrase del equipo:

php
$privateKeyRaw = $cipher->unwrapPrivateKey(
    wrappedBase64: $wrappedPrivateKeyBase64,
    saltBase64: $kdfSaltBase64,
    passphrase: $teamPassphrase,
);

unwrapPrivateKey() deriva una clave de cifrado de claves a partir de la passphrase y de la sal con Argon2id en los límites ops/mem MODERATE (ALG_ARGON2ID13), y luego abre la trama secretbox (nonce | ciphertext). Lanza una RuntimeException en caso de material corrupto o de passphrase incorrecta. Esto refleja intencionadamente el sellado del TeamCipher del servidor, para que la clave pueda reconstruirse en el lado del cliente y el servidor no la vea nunca.

Dónde se utiliza

Los clientes construyen sus comandos monitor:backup / monitor:restore sobre estas primitivas. El SDK de Laravel expone una fina subclase Support\BackupCipher de esta clase; el bundle de Symfony y el plugin de WordPress reutilizan el núcleo directamente. El flujo correspondiente del lado del servidor (almacenamiento de blob opaco, cuota en GB, API de material de clave) forma parte de la bóveda de copias de seguridad de Quiet Guard.

Está leyendo la documentación PHP Core v1.0.