Segurança de Senhas no Oracle: O Erro Crítico que Pode Destruir Seu Sistema
Recentemente, ao revisar código de autenticação em sistemas Oracle, me deparei com uma prática alarmantemente comum: o uso de…
Segurança de Senhas no Oracle: O Erro Crítico que Pode Destruir Seu Sistema
Recentemente, ao revisar código de autenticação em sistemas Oracle, me deparei com uma prática alarmantemente comum: o uso de DBMS_CRYPTO.ENCRYPT para armazenar senhas de usuários. À primeira vista, pode parecer uma solução “segura” , já que estamos usando criptografia, certo? Errado.

Esta abordagem representa uma vulnerabilidade crítica de segurança que pode comprometer todo o seu sistema. Neste artigo, vou explicar por que isso está errado e mostrar a forma correta de implementar armazenamento de senhas.
O Erro Fatal: Criptografia vs Hash
❌Por que ENCRYPT é Inadequado para Senhas
CREATE OR REPLACE FUNCTION autenticar_usuario_errado(
p_username IN VARCHAR2,
p_senha IN VARCHAR2
) RETURN BOOLEAN IS
l_senha_criptografada RAW(2000);
l_senha_armazenada RAW(2000);
l_chave RAW(64) := UTL_RAW.cast_to_raw('minha_chave_secreta_123456789012');
BEGIN
-- Criptografa a senha fornecida
l_senha_criptografada := DBMS_CRYPTO.ENCRYPT(
src => UTL_RAW.cast_to_raw(p_senha),
typ => DBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5,
key => l_chave
);
-- Compara com a senha armazenada
SELECT senha_criptografada
INTO l_senha_armazenada
FROM usuarios
WHERE username = p_username;
RETURN l_senha_criptografada = l_senha_armazenada;
END;
- Reversibilidade: Qualquer pessoa com acesso à chave pode DECRIPTAR todas as senhas;
- Chave única: Se a chave for comprometida, todas as senhas ficam expostas
- Senhas iguais = criptografias iguais: Dois usuários com a mesma senha terão o mesmo valor armazenado
- Compliance: Violação de padrões de segurança como OWASP, PCI-DSS, LGPD, GDPR.
✅ A Solução Correta: Hashing com Salt
Hash (One-Way Function): é uma transformação irreversível de dados, que sempre produz o mesmo resultado para a mesma entrada, mas não permite reverter o processo para obter o valor original; por isso, ele é usado para verificar senhas, e não para recuperá-las.
Salt: Valor aleatório e único adicionado a cada senha antes do hash, garantindo que senhas iguais gerem resultados diferentes e protegendo contra rainbow tables.
💡 Implementação Segura: Código Prático
CREATE OR REPLACE PACKAGE PKG_CONTROLE_ACESSO AS
-- Gera um salt aleatório criptograficamente seguro
FUNCTION gerar_salt RETURN RAW;
-- Gera o hash da senha com salt
FUNCTION gerar_hash_senha(
p_senha IN VARCHAR2,
p_salt IN RAW
) RETURN RAW;
-- Cria novo usuário com senha segura
PROCEDURE criar_usuario(
p_username IN VARCHAR2,
p_senha IN VARCHAR2
);
-- Autentica usuário
FUNCTION autenticar_usuario(
p_username IN VARCHAR2,
p_senha IN VARCHAR2
) RETURN BOOLEAN;
END PKG_CONTROLE_ACESSO;
/
CREATE OR REPLACE PACKAGE BODY PKG_CONTROLE_ACESSO AS
-- Constantes
C_MAX_TENTATIVAS CONSTANT NUMBER := 5;
C_ALGORITMO_HASH CONSTANT PLS_INTEGER := DBMS_CRYPTO.HASH_SH256;
C_TAMANHO_SALT CONSTANT NUMBER := 32; -- 256 bits
----------------------------------------------------------------------------
-- Gera um salt aleatório usando gerador criptograficamente seguro
----------------------------------------------------------------------------
FUNCTION gerar_salt RETURN RAW IS
BEGIN
-- DBMS_CRYPTO.RANDOMBYTES gera bytes aleatórios criptograficamente seguros
RETURN DBMS_CRYPTO.RANDOMBYTES(C_TAMANHO_SALT);
END gerar_salt;
----------------------------------------------------------------------------
-- Gera hash SHA-256 da senha combinada com salt
----------------------------------------------------------------------------
FUNCTION gerar_hash_senha(
p_senha IN VARCHAR2,
p_salt IN RAW
) RETURN RAW IS
l_senha_raw RAW(2000);
l_combinacao RAW(2000);
BEGIN
-- Converte senha para RAW
l_senha_raw := UTL_RAW.cast_to_raw(p_senha);
-- Combina senha + salt (concatenação)
l_combinacao := UTL_RAW.concat(l_senha_raw, p_salt);
-- Gera hash SHA-256
RETURN DBMS_CRYPTO.HASH(l_combinacao, C_ALGORITMO_HASH);
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, 'Erro ao gerar hash: ' || SQLERRM);
END gerar_hash_senha;
----------------------------------------------------------------------------
-- Cria novo usuário com senha hasheada e salt único
----------------------------------------------------------------------------
PROCEDURE criar_usuario(
p_username IN VARCHAR2,
p_senha IN VARCHAR2
) IS
l_salt RAW(32);
l_hash RAW(64);
BEGIN
-- Validações básicas
IF p_username IS NULL OR p_senha IS NULL THEN
RAISE_APPLICATION_ERROR(-20002, 'Username e senha são obrigatórios');
END IF;
-- Validação de força de senha (mínimo 8 caracteres)
IF LENGTH(p_senha) < 8 THEN
RAISE_APPLICATION_ERROR(-20003, 'Senha deve ter no mínimo 8 caracteres');
END IF;
-- Gera salt único para este usuário
l_salt := gerar_salt();
-- Gera hash da senha com o salt
l_hash := gerar_hash_senha(p_senha, l_salt);
-- Insere usuário
INSERT INTO usuarios_seguro (
id_usuario,
username,
senha_hash,
salt,
data_criacao,
ultima_alteracao_senha
) VALUES (
seq_usuarios.NEXTVAL,
LOWER(p_username),
l_hash,
l_salt,
SYSTIMESTAMP,
SYSTIMESTAMP
);
COMMIT;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
RAISE_APPLICATION_ERROR(-20004, 'Username já existe');
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END criar_usuario;
----------------------------------------------------------------------------
-- Autentica usuário verificando hash da senha
----------------------------------------------------------------------------
FUNCTION autenticar_usuario(
p_username IN VARCHAR2,
p_senha IN VARCHAR2
) RETURN BOOLEAN IS
l_salt RAW(32);
l_hash_armazenado RAW(64);
l_hash_fornecido RAW(64);
l_bloqueado VARCHAR2(1);
l_tentativas NUMBER;
l_id_usuario NUMBER;
BEGIN
-- Busca dados do usuário
BEGIN
SELECT id_usuario, senha_hash, salt, bloqueado, tentativas_login_falhas
INTO l_id_usuario, l_hash_armazenado, l_salt, l_bloqueado, l_tentativas
FROM usuarios_seguro
WHERE username = LOWER(p_username);
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Não revela se o usuário existe ou não (segurança)
DBMS_SESSION.SLEEP(1); -- Adiciona delay para dificultar brute force
RETURN FALSE;
END;
-- Verifica se conta está bloqueada
IF l_bloqueado = 'S' THEN
RAISE_APPLICATION_ERROR(-20005, 'Conta bloqueada. Contate o administrador.');
END IF;
-- Calcula hash da senha fornecida com o salt do usuário
l_hash_fornecido := gerar_hash_senha(p_senha, l_salt);
-- Compara hashes
IF l_hash_fornecido = l_hash_armazenado THEN
-- Autenticação bem-sucedida - reseta tentativas
UPDATE usuarios_seguro
SET tentativas_login_falhas = 0
WHERE id_usuario = l_id_usuario;
COMMIT;
RETURN TRUE;
ELSE
-- Autenticação falhou - incrementa tentativas
UPDATE usuarios_seguro
SET tentativas_login_falhas = tentativas_login_falhas + 1,
bloqueado = CASE
WHEN tentativas_login_falhas + 1 >= C_MAX_TENTATIVAS THEN 'S'
ELSE 'N'
END
WHERE id_usuario = l_id_usuario;
COMMIT;
DBMS_SESSION.SLEEP(1); -- Delay para dificultar brute force
RETURN FALSE;
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END autenticar_usuario;
END PKG_CONTROLE_ACESSO;
/
Pontos positivos da utilização de hash + salt é que não vai existir o mesmo hash para a mesma senha de usuários. E armazenar o salt por usuário vai te proteger contra rainbow tables.
⚠️ Armadilhas Comuns a Evitar
Usar salt fixo ou o ID do usuário como salt. Isso NÃO protege contra rainbow tables! O salt deve ser único e aleatório para cada usuário.
-- ❌ ERRADO - Salt fixo
l_salt := UTL_RAW.cast_to_raw('salt_fixo_123');
-- ❌ ERRADO - ID do usuário como salt
l_salt := UTL_RAW.cast_to_raw(TO_CHAR(p_id_usuario));
-- ✅ CORRETO - Salt aleatório único
l_salt := DBMS_CRYPTO.RANDOMBYTES(32);
🔮Próximo Artigo: Quando DBMS_CRYPTO.ENCRYPT é a Escolha CERTA
💡 Spoiler Alert: DBMS_CRYPTO.ENCRYPT não é o vilão da história!
Neste artigo, focamos em por que ENCRYPT é inadequado para senhas. Mas isso não significa que a criptografia reversível seja sempre ruim. Na verdade, ela é essencial em muitos cenários:
- 🏦 Dados financeiros que precisam ser descriptografados
- 📄 Documentos confidenciais para visualização autorizada
- 🔐 Tokens de API que precisam ser recuperados
- 💳 Dados de cartão (seguindo PCI-DSS)
No próximo artigo, vou mostrar quando e como usar DBMS_CRYPTO.ENCRYPT corretamente!
Conclusão
A segurança de senhas não é negociável. O uso de DBMS_CRYPTO.ENCRYPT para armazenamento de senhas é uma vulnerabilidade crítica que coloca todo o sistema em risco.]
📋 Checklist de Implementação Segura:
- ✅ Use hash one-way (SHA-256 ou superior)
- ✅ Implemente salt único por usuário
- ✅ Adicione proteção contra brute force
- ✅ Valide força das senhas
- ✅ Implemente auditoria de tentativas
- ✅ Use delays para dificultar ataques
💡 Lembre-se: Grandes empresas e sistemas não abrem mão de segurança. Não existe feature que valha o risco. Pense nisso.
메타데이터
- post_id
- c07ffd67ab08
- slug
- segurança-de-senhas-no-oracle-o-erro-crítico-que-pode-destruir-seu-sistema-c07ffd67ab08
- url
- https://medium.com/@juliononato/seguran%C3%A7a-de-senhas-no-oracle-o-erro-cr%C3%ADtico-que-pode-destruir-seu-sistema-c07ffd67ab08
- canonical_url
- https://medium.com/@juliononato/seguran%C3%A7a-de-senhas-no-oracle-o-erro-cr%C3%ADtico-que-pode-destruir-seu-sistema-c07ffd67ab08
- author_url
- https://medium.com/@juliononato
- status
- ok
- fetched_at
- 2026-06-09 15:37:30