Skip to content

Tudo para WordPress, desenvolvimento web — e não só

🔒 Encriptar e desencriptar ficheiros em PHP: OpenSSL e Sodium em vez de mcrypt

🔒 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));
4echo "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
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}

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_DATA diz à 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
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}

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
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;

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
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: 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 o sha1() 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 via password_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.php fora 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 o wp-config.php ao .gitignore ou 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 biblioteca defuse/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.