Encrypting sensitive data no banco de dados
1. Por que criptografar dados sensíveis?
A criptografia de dados sensíveis no banco de dados deixou de ser uma opção e se tornou uma exigência para aplicações modernas. Legislações como a LGPD (Lei Geral de Proteção de Dados) no Brasil, o GDPR na Europa e o PCI-DSS para dados de cartão de crédito impõem penalidades severas para vazamentos de informações pessoais.
Quando um invasor obtém acesso ao banco de dados, dados criptografados se tornam inúteis sem a chave correspondente. Isso reduz drasticamente o impacto de um vazamento. É crucial entender a diferença entre hash e criptografia: hash é unidirecional e ideal para senhas (não podem ser recuperadas), enquanto criptografia é bidirecional e permite recuperar o dado original — essencial para informações como CPF, cartões de crédito ou endereços que precisam ser exibidos novamente ao usuário.
2. Estratégias de criptografia: aplicação vs banco de dados
Existem duas abordagens principais:
Criptografia no lado da aplicação oferece controle total sobre o processo. A chave fica sob gestão do desenvolvedor, geralmente em variáveis de ambiente ou serviços como AWS KMS. A aplicação criptografa antes de enviar ao banco e descriptografa ao ler.
Criptografia nativa do banco utiliza funções como AES_ENCRYPT no MySQL ou pgcrypto no PostgreSQL. Embora pareça conveniente, essa abordagem expõe a chave nos logs do banco e dificulta a migração entre fornecedores.
Para a maioria dos casos, recomendo a criptografia no lado da aplicação: maior portabilidade, controle granular e segurança contra ataques ao próprio banco de dados.
3. Implementando criptografia com PHP e OpenSSL
O PHP oferece as funções openssl_encrypt e openssl_decrypt para criptografia simétrica. O algoritmo recomendado atualmente é AES-256-GCM, que fornece autenticação integrada (evita adulteração dos dados).
<?php
function encryptData(string $data, string $key): string
{
$iv = openssl_random_pseudo_bytes(12); // 12 bytes para GCM
$tag = '';
$ciphertext = openssl_encrypt(
$data,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'',
16
);
// Armazenamos IV + tag + ciphertext em base64
return base64_encode($iv . $tag . $ciphertext);
}
function decryptData(string $encryptedData, string $key): string
{
$decoded = base64_decode($encryptedData);
$iv = substr($decoded, 0, 12);
$tag = substr($decoded, 12, 16);
$ciphertext = substr($decoded, 28);
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($plaintext === false) {
throw new \RuntimeException('Falha na descriptografia ou dados adulterados');
}
return $plaintext;
}
// Exemplo de uso
$key = hex2bin('sua_chave_hexadecimal_de_32_bytes_aqui');
$encrypted = encryptData('123.456.789-00', $key);
echo $encrypted; // string base64 segura para banco
$decrypted = decryptData($encrypted, $key);
echo $decrypted; // 123.456.789-00
?>
A chave deve ter exatamente 32 bytes para AES-256. Armazene-a em variáveis de ambiente, nunca no código fonte.
4. Criptografia de atributos no Laravel (Eloquent)
No Laravel, podemos criar um cast personalizado para criptografar/descriptografar automaticamente ao acessar atributos:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class SensitiveDataCast implements CastsAttributes
{
private string $key;
public function __construct()
{
$this->key = hex2bin(config('app.encryption_key'));
}
public function get($model, string $key, $value, array $attributes): ?string
{
if ($value === null) {
return null;
}
return decryptData($value, $this->key);
}
public function set($model, string $key, $value, array $attributes): ?string
{
if ($value === null) {
return null;
}
return encryptData($value, $this->key);
}
}
No modelo:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Casts\SensitiveDataCast;
class User extends Model
{
protected $casts = [
'cpf' => SensitiveDataCast::class,
'credit_card' => SensitiveDataCast::class,
];
}
Agora, ao salvar $user->cpf = '123.456.789-00', o Laravel automaticamente criptografa. Ao ler, descriptografa. Importante: campos criptografados não podem ser usados em buscas WHERE tradicionais. Para buscas, considere índices determinísticos ou hashes separados.
5. Criptografia de dados em massa e migrações seguras
Ao criptografar dados existentes, evite downtime com uma migração gradual:
<?php
use App\Models\User;
use Illuminate\Support\Facades\DB;
// Passo 1: Adicionar coluna temporária
// ALTER TABLE users ADD COLUMN cpf_encrypted TEXT NULL;
// Passo 2: Migrar dados em lotes
User::whereNull('cpf_encrypted')
->chunk(100, function ($users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update([
'cpf_encrypted' => encryptData($user->cpf, $key)
]);
}
});
// Passo 3: Verificar integridade
$failed = User::whereNull('cpf_encrypted')->count();
if ($failed === 0) {
// Passo 4: Remover coluna original
// ALTER TABLE users DROP COLUMN cpf;
// ALTER TABLE users RENAME COLUMN cpf_encrypted TO cpf;
}
Sempre valide a integridade antes de remover dados originais.
6. Boas práticas de chave e rotação
A segurança da criptografia depende exclusivamente da chave. Siga estas práticas:
Envelope encryption: use uma chave mestra (armazenada em cofre) para criptografar chaves derivadas por registro:
<?php
function generateRecordKey(string $masterKey, string $recordId): string
{
// Deriva uma chave única por registro usando HKDF
return hash_hkdf('sha256', $masterKey, 32, 'record-key-' . $recordId);
}
Rotação periódica: implemente versionamento de chaves. Ao descriptografar, identifique qual chave foi usada e re-criptografe com a nova:
<?php
function rotateKey(string $encryptedData, string $oldKey, string $newKey): string
{
$plaintext = decryptData($encryptedData, $oldKey);
return encryptData($plaintext, $newKey);
}
Armazene chaves em serviços gerenciados como AWS KMS, HashiCorp Vault ou ao menos em .env com permissões restritas (600).
7. Monitoramento e logging seguro
Nunca registre dados descriptografados em logs. Em vez disso, registre metadados:
<?php
try {
$cpf = decryptData($encryptedCpf, $key);
} catch (\RuntimeException $e) {
Log::warning('Falha na descriptografia', [
'user_id' => $userId,
'ip' => request()->ip(),
'timestamp' => now()
]);
// Não logar o dado criptografado ou a chave
}
Integre com ferramentas de auditoria para detectar acessos indevidos. Monitore tentativas de descriptografia com falha — podem indicar ataques de força bruta ou adulteração de dados.
Referências
- PHP: openssl_encrypt - Manual oficial — Documentação oficial da função de criptografia OpenSSL no PHP, com parâmetros e exemplos.
- OWASP Cryptographic Storage Cheat Sheet — Guia completo de boas práticas para armazenamento criptografado, incluindo escolha de algoritmos e gerenciamento de chaves.
- Laravel: Custom Casts Documentation — Documentação oficial do Laravel sobre casts personalizados, base para implementação de criptografia automática em Eloquent.
- AWS KMS Developer Guide - Envelope Encryption — Conceito de envelope encryption e como implementar com AWS Key Management Service para gerenciamento seguro de chaves.
- PHP Security Guide: Cryptography — Guia prático de segurança em PHP focado em criptografia, com exemplos de implementação segura e armadilhas comuns.