
🔒 Encriptar e desencriptar ficheiros em PHP: OpenSSL e Sodium em vez de mcrypt
Encriptar um ficheiro antes de o guardar no servidor e ter a certeza de que ninguém, nem mesmo o administrador do alojamento, o consegue ler sem a chave. Parece uma necessidade básica, mas com a saída do mcrypt do PHP, a abordagem familiar deixou de funcionar.
A extensão mcrypt foi marcada como obsoleta no PHP 7.1 e completamente removida no PHP 7.2 em 2017. Hoje, no PHP 8, tentar chamar mcrypt_encrypt() termina num erro fatal. Entretanto, os ficheiros confidenciais continuam nos servidores: backups de bases de dados, CSVs com dados pessoais, contratos em PDF, exportações de encomendas.
Boas notícias: o PHP fornece dois mecanismos funcionais de raiz, o OpenSSL e o Sodium. Ambos não requerem a instalação de extensões adicionais no alojamento moderno, ambos são mais rápidos e mais seguros do que o mcrypt. Abaixo encontra um guia prático para encriptar e desencriptar ficheiros no PHP 8 com código que pode copiar e executar.
💡 Visão geral rápida:
- Porque é que o mcrypt está morto e que versões do PHP são afetadas
- Encriptação de ficheiros passo a passo via OpenSSL com AES-256-CBC
- Desencriptação com proteção contra adulteração de dados via HMAC
- Alternativa Sodium para PHP 7.2 e superior
- Quando escolher OpenSSL e quando escolher Sodium
Porque é que o mcrypt já não é uma opção
A biblioteca mcrypt não é atualizada desde 2007. Foram encontradas vulnerabilidades críticas no seu código e não restavam mantenedores. A equipa do PHP tomou uma decisão: no PHP 7.1 a extensão foi marcada como obsoleta e no PHP 7.2, lançado em novembro de 2017, foi completamente excluída do núcleo.
Se está a migrar um projeto antigo com mcrypt, verifique o phpinfo(). No PHP 7.2+ não existe a linha "mcrypt support: enabled". Chamar mcrypt_encrypt(), mcrypt_decrypt() ou os filtros de stream mcrypt.tripledes / mdecrypt.tripledes devolve o erro "Call to undefined function".
Tecnicamente, o mcrypt está disponível via PECL com o comando pecl install mcrypt. Mas instalar uma extensão não suportada com vulnerabilidades conhecidas num servidor de produção por causa de um script legado é uma má ideia. Reescreva a encriptação com OpenSSL: está integrado no PHP desde a versão 5.3 e não vai a lado nenhum.
Encriptação de ficheiros via OpenSSL, passo a passo
O OpenSSL no PHP é representado pelas funções openssl_encrypt() e openssl_decrypt(). Trabalham com dados brutos e suportam dezenas de algoritmos, desde AES-128-CBC a AES-256-GCM. Para ficheiros usamos AES-256-CBC: é criptograficamente forte e não requer PHP 7.1, ao contrário do GCM com parâmetros adicionais de tag.
Passo 1: gerar a chave de encriptação
A chave é o segredo principal de todo o esquema. Deve ser criptograficamente aleatória, não inventada manualmente. Nada de "password-secreta" dos exemplos, apenas openssl_random_pseudo_bytes().
O script abaixo gera uma chave de 256 bits e apresenta-a no formato para inserção no wp-config.php. Execute uma vez via linha de comandos e guarde o resultado:
1 <?php 2 // Generating a random 256-bit key (32 bytes) 3 $encryption_key = base64_encode(openssl_random_pseudo_bytes(32)); 4 echo "define('FILE_ENCRYPTION_KEY', '" . $encryption_key . "');\n";
A função openssl_random_pseudo_bytes(32) devolve 32 bytes de aleatoriedade de qualidade criptográfica. O base64_encode converte os dados binários numa string conveniente para guardar em ficheiros de configuração. A chave deve estar fora da raiz do documento, no wp-config.php ou .env, mas não no código do tema.
Passo 2: função de encriptação de ficheiros
O script lê um ficheiro do disco, encripta com o algoritmo AES-256-CBC, adiciona um IV aleatório no início e uma assinatura HMAC para verificação de integridade, guarda o resultado. Adicione o código ao functions.php de um tema filho ou a um plugin personalizado.
Aviso: antes de executar num servidor de produção, faça um backup completo. Teste a encriptação-desencriptação numa cópia do ficheiro num diretório de teste. Se a chave for perdida, é impossível desencriptar os dados, o AES-256 não pode ser quebrado por força bruta.
1 <?php 2 function 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 }
O que está a acontecer aqui, linha a linha:
openssl_cipher_iv_length('aes-256-cbc')devolve 16, o comprimento do vetor de inicialização para este algoritmo.openssl_random_pseudo_bytes($ivLength)cria um IV aleatório. Faz com que dados idênticos encriptados com a mesma chave produzam texto cifrado diferente em cada execução.OPENSSL_RAW_DATAdiz à função para devolver dados binários, não base64. Guardamos o texto cifrado bruto para compacidade.hash_hmac('sha256', ...)calcula uma soma de verificação do pacote IV-e-texto-cifrado. Durante a desencriptação, recalcularemos o HMAC e compararemos: se os dados foram modificados ou corrompidos, a comparação não corresponderá.- O ficheiro é guardado no formato:
[IV 16 bytes][HMAC 32 bytes][ciphertext]. Sem delimitadores, as posições são fixadas pelos comprimentos.
Passo 3: função de desencriptação
O processo inverso: ler o IV, ler o HMAC, ler o texto cifrado, recalcular o HMAC e comparar via hash_equals(), desencriptar. O código é adicionado ao mesmo ficheiro:
1 <?php 2 function 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 }
Ponto-chave: hash_equals() em vez de ===. A comparação de strings normal é vulnerável a ataques de temporização, um atacante pode descobrir o HMAC byte a byte medindo o tempo de resposta do servidor. O hash_equals() compara strings em tempo constante, independentemente do caractere em que divergem.
Exemplo de utilização com caminhos reais
1 <?php 2 $key = FILE_ENCRYPTION_KEY; // from wp-config.php 3 4 // Encrypting the database backup 5 encrypt_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); 13 header('Content-Type: application/octet-stream'); 14 header('Content-Disposition: attachment; filename="backup.sql"'); 15 echo $decrypted;
As funções são universais: funcionam com qualquer tipo de ficheiro, imagens, PDF, CSV, dumps SQL. O tamanho é limitado apenas pela RAM disponível, uma vez que o ficheiro é lido para a memória na totalidade. Para ficheiros de gigabytes, será necessário processamento por stream com buffer de blocos, mas para a esmagadora maioria das tarefas práticas este código é suficiente.
Alternativa: Sodium (libsodium)
A extensão Sodium está integrada no PHP a partir da versão 7.2 e tornou-se parte do núcleo no PHP 8.1. Fornece uma API mais simples em comparação com o OpenSSL: não é necessário gerir manualmente o IV e o HMAC, a encriptação autenticada funciona de raiz.
1 <?php 2 function 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 10 function 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: o Sodium verifica a integridade por si próprio. Se os dados foram modificados ou a chave está errada, sodium_crypto_secretbox_open() devolve simplesmente false, sem necessidade de verificações separadas de HMAC. O código tem metade do comprimento.
Uma desvantagem: o Sodium requer PHP 7.2 ou superior. Se o projeto corre em PHP 7.0-7.1, a única opção é o OpenSSL. Mas, na prática, em 2026, já é difícil encontrar alojamento com PHP inferior a 7.4: de acordo com os dados do WordPress.org de junho de 2026, a quota de PHP 7.0-7.1 é inferior a 0,3% de todas as instalações.
OpenSSL ou Sodium: dois critérios de seleção
A escolha entre as duas abordagens resume-se a dois fatores:
- Versão do PHP. PHP 7.2+, escolha Sodium, é mais seguro por defeito e não permite estragar a implementação do HMAC. PHP 7.0-7.1, apenas OpenSSL. Abaixo de PHP 7.0, está na hora de atualizar o servidor, não de inventar remendos com o PECL-mcrypt.
- Portabilidade dos dados entre ambientes. O OpenSSL está disponível em todo o lado, incluindo PHP 5.3+. Se os ficheiros encriptados tiverem de ser legíveis no servidor de desenvolvimento, em produção e no do cliente, o OpenSSL é mais fiável do ponto de vista da compatibilidade. O texto cifrado do Sodium só será desencriptado onde o Sodium existir.
Na prática, usamos OpenSSL em projetos onde a compatibilidade entre diferentes ambientes é importante. Sodium, onde toda a stack está atualizada para PHP 8 e a segurança está em primeiro lugar.
No vídeo, Dave Hollingworth analisa ambas as abordagens em detalhe com demonstração de código e explicação dos primitivos criptográficos por detrás de cada uma. O material complementa o artigo: são mostrados casos limite, a instalação da biblioteca defuse/php-encryption via Composer e a comparação de desempenho do OpenSSL e do Sodium em dados reais.
⁉️🤔 Perguntas frequentes
Posso usar md5() ou sha1() para encriptação de ficheiros?
Não. O
md5()e osha1()são funções de hash, são irreversíveis por definição. Um ficheiro encriptado tem de poder ser desencriptado de volta, e uma hash não pode ser desencriptada. As hashes são usadas para verificação de integridade (como o HMAC no código acima) e armazenamento de palavras-passe viapassword_hash(), mas não para encriptação de conteúdo.
O que fazer se a chave de encriptação for perdida?
Desencriptar dados sem a chave é impossível. Guarde a chave no
wp-config.phpfora da raiz do documento e faça uma cópia de segurança dela separadamente dos backups de ficheiros e da base de dados. Não coloque a chave num repositório Git, adicione owp-config.phpao.gitignoreou use variáveis de ambiente.
Porque é que o IV é necessário se a chave já é secreta?
Sem um IV aleatório, dados idênticos encriptados com a mesma chave produzem texto cifrado idêntico. Um atacante que veja blocos repetidos obtém informação sobre a estrutura do ficheiro. O IV torna cada execução de encriptação única: o mesmo ficheiro encriptado duas vezes com uma chave produz dois textos cifrados diferentes.
Posso encriptar ficheiros grandes, de vários gigabytes?
As funções acima leem o ficheiro para a memória na totalidade, para dados de gigabytes isto levará ao esgotamento da memória. Para encriptação por stream, use
openssl_encrypt()num ciclo com buffer de blocos (por exemplo, 1 MB cada) ou a bibliotecadefuse/php-encryption, que suporta o modo de stream de raiz.
Este código funciona no PHP 8.3?
Sim. Tanto o OpenSSL como o Sodium são totalmente suportados no PHP 8. O código é testado nas versões atuais do PHP e não usa funções obsoletas. No PHP 8.3, a compatibilidade é mantida, não existem incompatibilidades retroativas na extensão OpenSSL.
Encriptação de ficheiros em 2026: veredito prático
O Mcrypt saiu do PHP, e ainda bem. As duas ferramentas de substituição integradas são ambas mais seguras e mais rápidas e não requerem malabarismos com o PECL. O OpenSSL funciona em todo o lado, o Sodium é mais simples e mais fiável por defeito.
Em resumo: projeto novo em PHP 8, comece com Sodium, o código fica mais limpo. A migrar código antigo de mcrypt, reescreva para OpenSSL, está disponível até no PHP 7.0. E a regra principal da criptografia: guarde as chaves separadamente dos dados encriptados. A perda da chave equivale à perda dos dados, e a força bruta não vai ajudar aqui.
Comece com um script de teste numa cópia do ficheiro: garanta que o ciclo "encriptado → desencriptado → correspondência byte a byte" funciona sem erros. E que método usa nos seus projetos, OpenSSL, Sodium ou outro? Escreva nos comentários.



