Skip to content

Tutto per WordPress, lo sviluppo web — e non solo

🔒 Crittografia e decrittografia di file in PHP: OpenSSL e Sodium invece di mcrypt

🔒 Crittografia e decrittografia di file in PHP: OpenSSL e Sodium invece di mcrypt

Cifrare un file prima di salvarlo sul server ed essere certi che nessuno, nemmeno l’amministratore dell’hosting, possa leggerlo senza la chiave. Sembra un’esigenza di base, ma con l’uscita di scena di mcrypt da PHP, l’approccio abituale ha smesso di funzionare.

L’estensione mcrypt è stata contrassegnata come deprecata in PHP 7.1 e completamente rimossa in PHP 7.2 nel 2017. Oggi, su PHP 8, tentare di chiamare mcrypt_encrypt() produce un errore fatale. Nel frattempo, i file riservati restano sui server: backup di database, CSV con dati personali, contratti PDF, esportazioni di ordini.

Buone notizie: PHP mette a disposizione due meccanismi funzionanti nativamente, OpenSSL e Sodium. Entrambi non richiedono l’installazione di estensioni aggiuntive sugli hosting moderni, sono più veloci e più sicuri di mcrypt. Di seguito una guida pratica per cifrare e decifrare file su PHP 8 con codice che puoi copiare ed eseguire.

💡 Panoramica rapida:

  • Perché mcrypt è morto e quali versioni di PHP sono coinvolte
  • Cifratura di file passo passo con OpenSSL e AES-256-CBC
  • Decifratura con protezione da manomissione dati tramite HMAC
  • Alternativa Sodium per PHP 7.2 e superiori
  • Quando scegliere OpenSSL e quando Sodium

Perché mcrypt non è più un’opzione

La libreria mcrypt non viene aggiornata dal 2007. Nel suo codice sono state trovate vulnerabilità critiche e non c’erano più maintainer. Il team PHP ha preso una decisione: in PHP 7.1 l’estensione è stata marcata come deprecata e in PHP 7.2, rilasciato a novembre 2017, completamente esclusa dal core.

Se stai migrando un vecchio progetto con mcrypt, controlla phpinfo(). Su PHP 7.2+ non c’è la riga "mcrypt support: enabled". Chiamare mcrypt_encrypt(), mcrypt_decrypt() o i filtri di stream mcrypt.tripledes / mdecrypt.tripledes restituisce l’errore "Call to undefined function".

Tecnicamente mcrypt è disponibile via PECL con il comando pecl install mcrypt. Ma installare un’estensione non supportata e con vulnerabilità note su un server di produzione per un vecchio script è una cattiva idea. Riscrivi la cifratura con OpenSSL: è integrato in PHP dalla versione 5.3 ed è qui per restare.

Cifratura di file con OpenSSL, passo dopo passo

OpenSSL in PHP è rappresentato dalle funzioni openssl_encrypt() e openssl_decrypt(). Lavorano con dati grezzi e supportano decine di algoritmi, da AES-128-CBC a AES-256-GCM. Per i file usiamo AES-256-CBC: è crittograficamente robusto e non richiede PHP 7.1, a differenza di GCM con i parametri aggiuntivi per il tag.

Passo 1: generare la chiave di cifratura

La chiave è il segreto principale dell’intero schema. Deve essere crittograficamente casuale, non inventata a mano. Niente "password-segreta" da esempio, solo openssl_random_pseudo_bytes().

Lo script qui sotto genera una chiave a 256 bit e la restituisce in un formato adatto all’inserimento in wp-config.php. Eseguilo una volta da riga di comando e conserva il risultato:

1<?php
2// Generating a random 256-bit key (32 bytes)
3$encryption_key = base64_encode(openssl_random_pseudo_bytes(32));
4echo "define('FILE_ENCRYPTION_KEY', '" . $encryption_key . "');\n";

La funzione openssl_random_pseudo_bytes(32) restituisce 32 byte di casualità di qualità crittografica. base64_encode converte i dati binari in una stringa comoda da memorizzare nei file di configurazione. La chiave deve stare fuori dalla document root, in wp-config.php o .env, non nel codice del tema.

Passo 2: funzione di cifratura del file

Lo script legge un file dal disco, lo cifra con l’algoritmo AES-256-CBC, aggiunge un IV casuale all’inizio e una firma HMAC per la verifica dell’integrità, quindi salva il risultato. Aggiungi il codice al functions.php di un child theme o a un plugin personalizzato.

Attenzione: prima di eseguirlo su un server di produzione, fai un backup completo. Testa la cifratura e la decifratura su una copia del file in una directory di test. Se la chiave viene persa, decifrare i dati è impossibile, AES-256 non può essere forzato per brute force.

1<?php
2function encrypt_file(string $sourcePath, string $destPath, string $key): bool
3{
4 if (!file_exists($sourcePath)) {
5 throw new RuntimeException('Source file not found: ' . $sourcePath);
6 }
7
8 $plaintext = file_get_contents($sourcePath);
9 if ($plaintext === false) {
10 throw new RuntimeException('Failed to read file');
11 }
12
13 $cipher = 'aes-256-cbc';
14 $ivLength = openssl_cipher_iv_length($cipher);
15 $iv = openssl_random_pseudo_bytes($ivLength);
16
17 $ciphertext = openssl_encrypt(
18 $plaintext,
19 $cipher,
20 base64_decode($key),
21 OPENSSL_RAW_DATA,
22 $iv
23 );
24
25 if ($ciphertext === false) {
26 throw new RuntimeException('Encryption error');
27 }
28
29 // HMAC signature for integrity verification during decryption
30 $hmac = hash_hmac('sha256', $iv . $ciphertext, base64_decode($key), true);
31
32 // File format: IV (16 bytes) + HMAC (32 bytes) + ciphertext
33 $result = file_put_contents($destPath, $iv . $hmac . $ciphertext);
34
35 return $result !== false;
36}

Cosa succede qui, riga per riga:

  • openssl_cipher_iv_length('aes-256-cbc') restituisce 16, la lunghezza del vettore di inizializzazione per questo algoritmo.
  • openssl_random_pseudo_bytes($ivLength) crea un IV casuale. Fa sì che dati identici cifrati con la stessa chiave producano un testo cifrato diverso a ogni esecuzione.
  • OPENSSL_RAW_DATA indica alla funzione di restituire dati binari, non base64. Salviamo il testo cifrato grezzo per compattezza.
  • hash_hmac('sha256', ...) calcola un checksum dal pacchetto IV e testo cifrato. In fase di decifratura ricalcoleremo l’HMAC e lo confronteremo: se i dati sono stati modificati o corrotti, il confronto non corrisponderà.
  • Il file viene salvato nel formato: [IV 16 bytes][HMAC 32 bytes][ciphertext]. Nessun delimitatore, le posizioni sono fisse in base alle lunghezze.

Passo 3: funzione di decifratura

Il processo inverso: leggi IV, leggi HMAC, leggi testo cifrato, ricalcola HMAC e confronta con hash_equals(), decifra. Il codice si aggiunge allo stesso file:

1<?php
2function decrypt_file(string $sourcePath, string $key): string|false
3{
4 if (!file_exists($sourcePath)) {
5 throw new RuntimeException('Encrypted file not found: ' . $sourcePath);
6 }
7
8 $data = file_get_contents($sourcePath);
9 if ($data === false) {
10 throw new RuntimeException('Failed to read file');
11 }
12
13 $cipher = 'aes-256-cbc';
14 $ivLength = openssl_cipher_iv_length($cipher);
15 $hmacLength = 32; // sha256 = 32 bytes
16
17 $iv = substr($data, 0, $ivLength);
18 $hmac = substr($data, $ivLength, $hmacLength);
19 $ciphertext = substr($data, $ivLength + $hmacLength);
20
21 // Integrity check: recompute HMAC and compare
22 $calculatedHmac = hash_hmac(
23 'sha256',
24 $iv . $ciphertext,
25 base64_decode($key),
26 true
27 );
28
29 if (!hash_equals($hmac, $calculatedHmac)) {
30 throw new RuntimeException('File corrupted or key invalid');
31 }
32
33 $plaintext = openssl_decrypt(
34 $ciphertext,
35 $cipher,
36 base64_decode($key),
37 OPENSSL_RAW_DATA,
38 $iv
39 );
40
41 return $plaintext;
42}

Punto chiave: hash_equals() invece di ===. Il confronto normale tra stringhe è vulnerabile ad attacchi di timing: un malintenzionato può individuare l’HMAC byte per byte misurando il tempo di risposta del server. hash_equals() confronta le stringhe in tempo costante, indipendentemente dal carattere in cui divergono.

Esempio di utilizzo con percorsi reali

1<?php
2$key = FILE_ENCRYPTION_KEY; // from wp-config.php
3
4// Encrypting the database backup
5encrypt_file(
6 __DIR__ . '/backup.sql',
7 __DIR__ . '/backup.sql.enc',
8 $key
9);
10
11// Decrypting and serving for download
12$decrypted = decrypt_file(__DIR__ . '/backup.sql.enc', $key);
13header('Content-Type: application/octet-stream');
14header('Content-Disposition: attachment; filename="backup.sql"');
15echo $decrypted;

Le funzioni sono universali: funzionano con qualsiasi tipo di file, immagini, PDF, CSV, dump SQL. La dimensione è limitata solo dalla RAM disponibile, poiché il file viene letto interamente in memoria. Per file di diversi gigabyte servirà un’elaborazione a flusso con buffer a blocchi, ma per la stragrande maggioranza dei casi pratici questo codice è sufficiente.

Alternativa: Sodium (libsodium)

L’estensione Sodium è integrata in PHP a partire dalla versione 7.2 ed è diventata parte del core in PHP 8.1. Offre una API più semplice rispetto a OpenSSL: non serve gestire manualmente IV e HMAC, la cifratura autenticata funziona in modo nativo.

1<?php
2function sodium_encrypt_file(string $sourcePath, string $destPath, string $key): bool
3{
4 $plaintext = file_get_contents($sourcePath);
5 $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
6 $ciphertext = sodium_crypto_secretbox($plaintext, $nonce, base64_decode($key));
7 return file_put_contents($destPath, $nonce . $ciphertext) !== false;
8}
9
10function sodium_decrypt_file(string $sourcePath, string $key): string|false
11{
12 $data = file_get_contents($sourcePath);
13 $nonce = substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
14 $ciphertext = substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
15 $result = sodium_crypto_secretbox_open($ciphertext, $nonce, base64_decode($key));
16 return $result !== false ? $result : false;
17}

Nota: Sodium verifica l’integrità autonomamente. Se i dati sono stati modificati o la chiave è sbagliata, sodium_crypto_secretbox_open() restituisce semplicemente false, senza bisogno di controlli HMAC separati. Il codice è lungo la metà.

Uno svantaggio: Sodium richiede PHP 7.2 o superiore. Se il progetto gira su PHP 7.0-7.1, l’unica opzione è OpenSSL. Ma in pratica nel 2026 trovare un hosting con PHP inferiore alla 7.4 è già difficile: secondo i dati di WordPress.org di giugno 2026, la quota di PHP 7.0-7.1 è inferiore allo 0,3% di tutte le installazioni.

OpenSSL o Sodium: due criteri di scelta

La scelta tra i due approcci si riduce a due fattori:

  • Versione PHP. PHP 7.2+, prendi Sodium, è più sicuro di default e non ti permette di sbagliare l’implementazione dell’HMAC. PHP 7.0-7.1, solo OpenSSL. Sotto PHP 7.0, è ora di aggiornare il server, non di inventare soluzioni alternative con PECL-mcrypt.
  • Portabilità dei dati tra ambienti. OpenSSL è disponibile ovunque, incluso PHP 5.3+. Se i file cifrati devono essere leggibili sul server di sviluppo, in produzione e dal cliente, OpenSSL è più affidabile dal punto di vista della compatibilità. Il testo cifrato da Sodium verrà decifrato solo dove Sodium è presente.

In pratica usiamo OpenSSL nei progetti dove la compatibilità tra ambienti diversi è importante. Sodium dove l’intero stack è aggiornato a PHP 8 e la sicurezza viene prima di tutto.

Nel video Dave Hollingworth analizza entrambi gli approcci in dettaglio con dimostrazione del codice e spiegazione dei primitivi crittografici alla base di ciascuno. Il materiale integra l’articolo: vengono mostrati casi limite, l’installazione della libreria defuse/php-encryption via Composer e il confronto delle performance di OpenSSL e Sodium su dati reali.

⁉️🤔 Domande frequenti

Posso usare md5() o sha1() per cifrare i file?

No. md5() e sha1() sono funzioni di hash, sono irreversibili per definizione. Un file cifrato deve poter essere decifrato, e un hash non può essere decifrato. Gli hash si usano per la verifica dell’integrità (come l’HMAC nel codice sopra) e per la memorizzazione delle password tramite password_hash(), ma non per cifrare contenuti.

Cosa fare se la chiave di cifratura viene persa?

Decifrare i dati senza la chiave è impossibile. Conserva la chiave in wp-config.php fuori dalla document root e fanne un backup separato dai backup di file e database. Non mettere la chiave in un repository Git, aggiungi wp-config.php a .gitignore oppure usa variabili d’ambiente.

Perché serve l’IV se la chiave è già segreta?

Senza un IV casuale, dati identici cifrati con la stessa chiave producono un testo cifrato identico. Un malintenzionato che vede blocchi ripetuti ottiene informazioni sulla struttura del file. L’IV rende ogni esecuzione di cifratura unica: lo stesso file cifrato due volte con una stessa chiave produce due testi cifrati diversi.

Posso cifrare file grandi, di diversi gigabyte?

Le funzioni sopra leggono il file interamente in memoria, per dati di gigabyte questo porterà all’esaurimento della memoria. Per la cifratura a flusso usa openssl_encrypt() in un ciclo con buffer a blocchi (per esempio, 1 MB ciascuno) oppure la libreria defuse/php-encryption, che supporta la modalità streaming in modo nativo.

Questo codice funziona su PHP 8.3?

Sì. Sia OpenSSL che Sodium sono pienamente supportati in PHP 8. Il codice è testato sulle versioni correnti di PHP e non usa funzioni deprecate. Su PHP 8.3 la compatibilità è mantenuta, non ci sono incompatibilità retroattive nell’estensione OpenSSL.

Cifratura di file nel 2026: verdetto pratico

Mcrypt ha lasciato PHP, ed è un bene. I due strumenti di sostituzione integrati sono entrambi più sicuri e più veloci e non richiedono acrobazie con PECL. OpenSSL funziona ovunque, Sodium è più semplice e affidabile di default.

In sintesi: nuovo progetto su PHP 8, parti con Sodium, il codice risulta più pulito. Migrazione di vecchio codice da mcrypt, riscrivi con OpenSSL, è disponibile anche su PHP 7.0. E la regola principale della crittografia: conserva le chiavi separatamente dai dati cifrati. La perdita della chiave equivale alla perdita dei dati, e il brute force qui non aiuta.

Inizia con uno script di test su una copia del file: verifica che il ciclo "cifrato → decifrato → corrispondenza byte a byte" funzioni senza errori. E tu quale metodo usi nei tuoi progetti, OpenSSL, Sodium o qualcos’altro? Scrivilo nei commenti.