← Back to list

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…

Julio Nonato · 2026-02-22 23:04 · 1 claps · 4.2 min read
#oracle-apex #orclapex #oracle-database #security #apexacademybr
Open on Medium ↗

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;
  1. Reversibilidade: Qualquer pessoa com acesso à chave pode DECRIPTAR todas as senhas;
  2. Chave única: Se a chave for comprometida, todas as senhas ficam expostas
  3. Senhas iguais = criptografias iguais: Dois usuários com a mesma senha terão o mesmo valor armazenado
  4. 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