O perigo do "Texto Claro" (Plaintext)
A maioria dos desenvolvedores segue o padrão clássico: criptografam a senha com Argon2 ou Bcrypt, mas deixam o E-mail, CPF e Telefone em texto puro. No papel, parece correto; na prática, é um desastre anunciado.
{
name: 'tester 123',
username: 'teste123',
email: 'teste@gmail.com',
password: '$argon2id$v=19$m=65536,t=3,p=4$0fnlxR1QiseB2GrhDX9oPg$Uw8VMWVZ7LELNxEKMFBvxddYzxFtb1lggnyd+PlneOc',
status: 'ACTIVE'
}
Se um atacante conseguir um dump (cópia) do seu banco de dados, ele terá em mãos uma lista de identidades reais. Em conformidade com a LGPD no Brasil ou a GDPR na Europa, isso não é apenas uma falha técnica, é um risco jurídico que pode custar milhões em multas.
O Dilema: Criptografar vs. Buscar
Você pode pensar: "Basta criptografar o e-mail antes de salvar!".
O problema é que a criptografia padrão (AES-256, por exemplo) gera resultados diferentes a cada execução devido ao uso de IVs (Initialization Vectors) ou Nonces aleatórios. Isso é ótimo para segurança, mas impossível para buscas
Como você daria um SELECT * FROM users WHERE email = '...' se o resultado criptografado muda toda vez?
A Solução: Blind Indexing
O Blind Index (Índice Cego) resolve o paradoxo entre segurança e usabilidade. A ideia central é criar uma "impressão digital" determinística do dado sensível, utilizando uma chave secreta que jamais deve tocar o banco de dados.
Por que usar HMAC-SHA512256 e não apenas SHA-256?
- O problema do SHA-256: O problema do Hash Simples (SHA-256): Embora o SHA-256 seja um algoritmo extremamente seguro e robusto, ele não foi projetado para proteger dados previsíveis. Por ser um hash público e de alta performance, ele permite que um atacante realize ataques de dicionário ou use Rainbow Tables (listas de hashes pré-calculados de e-mails comuns). Se o seu dado é previsível (como um e-mail), um atacante não precisa "quebrar" o algoritmo; ele só precisa comparar o seu hash com uma lista de hashes que ele já possui.
-
A vantagem do HMAC: O HMAC (Hash-based Message Authentication Code) introduz um fator crítico: uma Secret Key. Sem a sua
BLIND_INDEX_SECRET— que permanece protegida e isolada no seu servidor ou container — o hash armazenado torna-se um dado opaco e sem valor para o invasor. Com a tecnologia computacional que temos hoje, esse código dificilmente pode ser quebrado, garantindo a proteção da identidade dos seus usuários mesmo em caso de um vazamento total da base de dados.
Como fica o seu dado agora?
{
name: 'tester 123',
username: 'teste123',
emailCrypto: 'okHMME54xzIh4als7zQJIK5h94k4vfxi0avJtwFnafCIG05AZhZx7/L02V5nhTDe2ReBPH5Q9w==',
emailHash: '3adeecd4e5fc026c21b62174c53c9a9ead24dbc925d97bad25ec9d301e80a026',
password: '$argon2id$v=19$m=65536,t=3,p=4$0fnlxR1QiseB2GrhDX9oPg$Uw8VMWVZ7LELNxEKMFBvxddYzxFtb1lggnyd+PlneOc',
status: 'ACTIVE'
}
Processo de Registro (Verificação de Unicidade)
No registro, o objetivo é a Prevenção. Usamos o operador OR para garantir que nenhum dado colida com o que já existe.
O Fluxo:
-
Entrada: O usuário envia o e-mail
PETRUS@gmail.com. -
Normalização: O sistema transforma em
petrus@gmail.com(case-insensitive). -
Geração do Blind Index: A aplicação aplica o HMAC:
emailHash = HMAC("petrus@gmail.com", BLIND_INDEX_SECRET)Resultado:
3adeec... Consulta ao Banco:
SELECT id FROM accounts WHERE emailHash = '3adeec...' OR identificationHash = '...';-
Decisão:
- Se retornar algo: Erro "Account already exists"
- Se nulo: Segue para a criptografia do e-mail real e salva.
A Origem do emailCrypto: Criptografia de Via Dupla
Diferente do emailHash, que é uma rua de mão única (você gera o hash mas nunca o "desfaz"), o emailCryptonão usamos um hash comum, mas sim algoritmos de criptografia simétrica autenticada (AEAD). O favorito do mercado de alta segurança é o XChaCha20-Poly1305, pela sua robustez e facilidade de implementação sem erros fatais de gerenciamento de Nonces. Outras alternativas sólidas incluem o AES-256-GCM, amplamente utilizado em setores bancários, e o AEGIS-256, para quem busca a máxima performance em hardware moderno.
No momento do registro, a aplicação utiliza a sua CRYPTO_SECRET para transformar o e-mail legível em uma cifra protegida. É vital entender que:
-
Blind Index (
emailHash): Criado com aBLIND_INDEX_SECRET. Serve apenas para o banco de dados te dizer: "Eu tenho um registro que bate com essa digital". -
Armazenamento (
emailCrypto): Criado com aCRYPTO_SECRET. Serve para a aplicação dizer: "Agora que encontrei o registro, vou usar minha chave privada para ler o que está escrito aqui".
Exemplo de registro
async function register(emailRaw: string, passwordRaw: string) {
const emailSearchIndex = await blindIndex.generate(emailRaw);
const account = await repository.find({ emailHash: emailSearchIndex });
if (account) throw new Error("Account Already Exists")
const dataToSave = {
// Digital determinística para buscas futuras
emailHash: await blindIndex.generate(emailRaw),
// O dado real "trancado" com a chave de criptografia
emailCrypto: await crypto.encrypt(emailRaw, process.env.CRYPTO_SECRET),
password: await argon2.hash(passwordRaw)
};
// salva o novo usuario
};
Processo de Login (Busca e Autenticação)
No login, o objetivo é a Identificação Estrita. Usamos o operador AND para garantir precisão.
O Fluxo:
- Entrada: Usuário digita e-mail e senha.
-
Geração do Blind Index: O sistema gera o hash do e-mail digitado usando a mesma
BLIND_INDEX_SECRET. -
Busca no Banco:
SELECT * FROM accounts WHERE emailHash = '3adeec...' AND status = 'ACTIVE'; -
Extração: O banco retorna a linha contendo o
emailCrypto(que você não usa agora) e opassword(hash do Argon2) como no exemplo acima. -
Verificação de Senha:
isValid = Argon2.verify(password_do_banco, senha_digitada, { secret: PASSWORD_SECRET }) -
Conclusão: Se
isValidfor true, você completa seu processo de login.
Exemplo de login
async function login(emailRaw: string, passwordRaw: string) {
// 1. O sistema "fica cego" para o e-mail real e gera o índice de busca
const emailSearchIndex = await blindIndex.generate(emailRaw);
// 2. Busca direta no banco pelo índice (performance de O(1) com B-Tree index)
const account = await repository.find({ emailHash: emailSearchIndex }, "AND");
if (!account) throw new Error("Invalid credentials");
// 3. A senha é verificada separadamente com Argon2 + Pepper
const isPassValid = await passwordService.compare(account.password, passwordRaw);
if (!isPassValid) throw new Error("Invalid credentials");
return generateToken(account.id);
}
O Uso do Dado
A dúvida que surge após implementar o Blind Index é: "Se o e-mail está criptografado, como eu envio uma notificação ou um ingresso para o usuário?".
A resposta está na Criptografia Simétrica de Chave Única. Enquanto o emailHash (Blind Index) serve para encontrar o registro, o emailCrypto serve para revelar o dado apenas no momento da execução.
O Fluxo de Recuperação (Ex: Envio de Ingresso)
-
Recuperação: O sistema busca a conta pelo ID ou pelo
emailHash. -
Descriptografia em Memória: A aplicação utiliza a
CRYPTO_SECRET(que reside apenas nas variáveis de ambiente do servidor ou em algun serviço de injeção de variaveis) para descriptografar oemailCrypto. -
Memória Efêmera: O e-mail em texto puro (ex:
teste@gmail.com) agora existe apenas na memória RAM do processo por alguns milissegundos. - Consumo: O dado é enviado para o provedor de e-mail (como AWS SES ou SendGrid) via conexão segura (HTTPS/TLS).
- Descarte: Assim que a função termina, o dado em texto puro é descartado da memória. Ele nunca toca o disco e jamais deve aparecer nos seus logs.
Exemplo:
async function sendTicket(accountId: string) {
// 1. Busca os dados protegidos
const account = await repository.findById(accountId);
// 2. Transforma o amontoado de caracteres no dado real
const plaintextEmail = await crypto.decrypt(account.emailCrypto);
// 3. Uso imediato e seguro
await emailProvider.send({
to: plaintextEmail,
subject: "Seu Ingresso Chegou!",
body: "..."
});
// O dado em texto puro morre aqui.
}
Conclusão: Segurança não é opcional, é fundamento
Implementar Blind Indexing vai muito além de evitar multas regulatórias; trata-se de adotar uma postura de Defesa Proativa. Ao separar a capacidade de identificar um registro da capacidade de ler o dado sensível, transformamos nosso banco de dados em um ambiente hostil para invasores, mas extremamente eficiente para o negócio.
Ao adotar essa arquitetura, você cria um sistema que é Cego para o Banco de Dados, mas Inteligente para a Aplicação. O banco de dados torna-se apenas um depósito de cifras inúteis para invasores, enquanto sua aplicação retém a soberania sobre a identidade dos usuários.
- Petrus Noleto
Top comments (0)