Como Armazenar CPF com Segurança no Banco de Dados
O CPF é dado pessoal segundo a LGPD e merece proteção adequada no banco de dados. Porém, o keyspace pequeno do CPF (~10⁹ valores válidos) muda completamente a estratégia em relação a senhas. Técnicas que funcionam bem para senhas — como bcrypt e Argon2 — não são adequadas para CPFs.
O problema do keyspace
Uma senha forte tem entropia alta: bilhões de bilhões de combinações possíveis. O CPF, não:
- Formato: 11 dígitos numéricos (000.000.000-00 a 999.999.999-99)
- Valores válidos: ~1,2 bilhão (descartando dígitos verificadores inválidos)
- SHA-256 de todos os CPFs possíveis: calculável em minutos num computador comum
Isso significa que um hash sem segredo é efetivamente texto plano. Um atacante com acesso ao banco pode gerar a rainbow table completa em poucos minutos e reverter todos os hashes.
Técnicas e quando usar cada uma
| Técnica | Reversível | Busca | Seguro para CPF | Quando usar |
|---|---|---|---|---|
| SHA-256 | Não | Sim | Não | Nunca para CPF |
| SHA-256 + salt | Não | Não | Não | Nunca para CPF |
| bcrypt / Argon2 | Não | Não | Não | Nunca para CPF |
| HMAC-SHA256 + pepper | Não | Sim | Sim | Verificação / dedup |
| AES-256-GCM | Sim | Não | Sim | Exibição / exportação |
| Tokenização | — | Sim (token) | Sim | Sistemas distribuídos |
Por que NÃO usar bcrypt/Argon2 para CPF
bcrypt e Argon2 foram projetados para senhas, onde a entropia é alta e o custo computacional por tentativa compensa. Para CPFs, o cenário é diferente:
- Salt impede busca: cada registro tem um salt diferente, tornando impossível buscar por
WHERE hash = ?. Inviável para lookup. - Keyspace pequeno: mesmo com 100 ms por hash (bcrypt cost 12), o total de 1,2 × 10⁹ hashes levaria ~3,8 anos em 1 CPU. Parece muito, mas:
- GPUs modernas paralelizam bcrypt com eficiência moderada
- Um cluster de GPUs reduz isso para horas ou dias
- O atacante precisa fazer o ataque apenas uma vez para montar a tabela
- Custo não compensa: o custo computacional do bcrypt/Argon2 é projetado para senhas com entropia de 40+ bits. Para ~30 bits de entropia (CPF), a proteção é insuficiente.
HMAC-SHA256 com pepper
O HMAC resolve o problema do keyspace adicionando uma chave secreta (pepper) ao cálculo. Sem o pepper, o atacante não consegue computar os hashes mesmo com acesso total ao banco.
Quando usar: verificação de existência, deduplicação, busca por CPF.
import { createHmac } from "node:crypto";
// Pepper: chave secreta armazenada fora do banco (env var, KMS)
const PEPPER = process.env.CPF_HMAC_KEY; // 256 bits, hex
function hmacCpf(cpf) {
const digits = cpf.replace(/\D/g, "");
return createHmac("sha256", Buffer.from(PEPPER, "hex"))
.update(digits)
.digest("hex");
}
// Armazenar: INSERT INTO users (cpf_hmac) VALUES ($1)
// Buscar: SELECT * FROM users WHERE cpf_hmac = $1
O pepper funciona como um segredo compartilhado: se o banco vaza, o atacante tem os HMACs mas não consegue revertê-los sem a chave. Diferente de um salt, o pepper é o mesmo para todos os registros, o que permite busca exata por WHERE cpf_hmac = ?.
AES-256-GCM para armazenamento reversível
Quando você precisa exibir, exportar ou enviar o CPF, o hash não serve — você precisa de criptografia reversível. AES-256-GCM é a escolha padrão:
- AES-256: cifra simétrica de 256 bits
- GCM: modo autenticado (detecta adulteração do dado cifrado)
- IV: vetor de inicialização único por registro (impede que dois CPFs iguais gerem o mesmo ciphertext)
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
const KEY = Buffer.from(process.env.CPF_ENCRYPTION_KEY, "hex"); // 32 bytes
function encrypt(cpf) {
const digits = cpf.replace(/\D/g, "");
const iv = randomBytes(12); // 96 bits para GCM
const cipher = createCipheriv("aes-256-gcm", KEY, iv);
let encrypted = cipher.update(digits, "utf8", "hex");
encrypted += cipher.final("hex");
const tag = cipher.getAuthTag().toString("hex");
return { encrypted, iv: iv.toString("hex"), tag };
}
function decrypt(encrypted, ivHex, tagHex) {
const iv = Buffer.from(ivHex, "hex");
const decipher = createDecipheriv("aes-256-gcm", KEY, iv);
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
Combinando as duas técnicas
O padrão recomendado usa duas colunas: uma para busca (HMAC) e outra para recuperação (AES):
CREATE TABLE users (
id SERIAL PRIMARY KEY,
cpf_hmac CHAR(64) NOT NULL, -- HMAC-SHA256 para busca
cpf_enc TEXT NOT NULL, -- AES-256-GCM cifrado
cpf_iv CHAR(24) NOT NULL, -- IV em hex
cpf_tag CHAR(32) NOT NULL -- Auth tag em hex
);
CREATE UNIQUE INDEX idx_cpf_hmac ON users (cpf_hmac);
Funções de armazenamento e busca:
function storeCpf(cpf) {
const hmac = hmacCpf(cpf);
const { encrypted, iv, tag } = encrypt(cpf);
return { cpf_hmac: hmac, cpf_enc: encrypted, cpf_iv: iv, cpf_tag: tag };
}
function searchCpf(cpf) {
const hmac = hmacCpf(cpf);
// SELECT * FROM users WHERE cpf_hmac = $1
return hmac;
}
function retrieveCpf(row) {
return decrypt(row.cpf_enc, row.cpf_iv, row.cpf_tag);
}
Gerenciamento de chaves
As chaves de HMAC e criptografia são a parte mais crítica da arquitetura. Se o atacante obtiver as chaves, todas as proteções caem:
- Nunca no código-fonte: chaves hardcoded vão para o histórico do Git e ficam expostas
- Variáveis de ambiente: nível mínimo aceitável (
CPF_HMAC_KEY,CPF_ENCRYPTION_KEY) - KMS em produção: AWS KMS, GCP Cloud KMS ou Azure Key Vault — o serviço gerencia rotação, auditoria e controle de acesso
- Rotação de chaves: re-encrypt periódico dos dados com a nova chave; mantenha a chave antiga ativa temporariamente para leitura
LGPD e CPF
A Lei Geral de Proteção de Dados (Lei nº 13.709/2018) classifica o CPF como dado pessoal:
- Base legal: você precisa de uma base legal válida para processar CPFs (consentimento, execução de contrato, obrigação legal, etc.)
- Minimização: não armazene o CPF se não precisa dele — pergunte se o dado é realmente necessário
- Direito à exclusão: o titular pode solicitar a exclusão do seu CPF — sua arquitetura deve suportar isso
- Segurança: a LGPD exige medidas técnicas adequadas — criptografia e HMAC atendem a esse requisito
Antes de armazenar, é fundamental validar o CPF no momento do cadastro para garantir que apenas números com dígitos verificadores corretos cheguem ao banco. Para ambientes de desenvolvimento e homologação, use o gerador de CPF para popular o banco com dados de teste sem risco de usar dados reais.