Ir para o conteúdo principal

Proteger um diretório com .htaccess: Basic Auth, APR1-MD5 e bcrypt

Por CaptainDNS
Publicado em 12 de agosto de 2026

Diagrama do diálogo HTTP Basic Auth entre um navegador e um servidor Apache ou nginx protegido por senha
TL;DR
  • Basic Auth HTTP protege um diretório através de um arquivo .htpasswd (Apache) ou auth_basic_user_file (nginx), em poucos minutos de configuração.
  • A senha nunca é armazenada em texto puro: ela é transformada em hash, em APR1-MD5 (histórico, compatível em todos os lugares) ou em bcrypt (recomendado, custo computacional ajustável).
  • Sem HTTPS, Basic Auth transita em texto puro: ative-o somente atrás de TLS.
  • O arquivo .htpasswd deve ficar fora da raiz web, nunca em um diretório que possa ser baixado.
  • Gere seus hashes sem linha de comando com os geradores APR1-MD5 e bcrypt do CaptainDNS.

Um servidor de pré-produção que ficou indexado pelo Google. Um painel de monitoramento acessível a qualquer um que adivinhe a URL. Uma documentação técnica publicada por engano em texto puro na web. Três cenários comuns, e uma única solução de primeiros socorros nos três casos: Basic Auth.

Basic Auth existe desde os primórdios da web e quase não mudou desde então. Ela protege um diretório ou um arquivo com um simples par usuário/senha, sem banco de dados, sem sessão, sem dependência aplicacional. No Apache, um arquivo .htaccess e um arquivo .htpasswd são suficientes. No nginx, duas diretivas, auth_basic e auth_basic_user_file. Cinco minutos de configuração, e o acesso está bloqueado.

Este guia detalha a configuração prática no Apache e no nginx, com as armadilhas que fazem perder uma hora: AllowOverride mal configurado, caminho do arquivo esquecido, serviço nunca recarregado. Ele também explica o que realmente acontece em um arquivo .htpasswd. Por que o Apache inventou seu próprio formato de hash, APR1-MD5. Por que o bcrypt o substituiu amplamente. E como escolher entre os dois com conhecimento de causa, em vez de por hábito ou por copiar e colar de um tutorial de 2015.

Este conteúdo é voltado para administradores de sistema, DevOps e desenvolvedores backend que gerenciam sua própria hospedagem e querem bloquear um acesso sem esperar a implantação de um SSO ou de uma VPN.

Por que proteger um diretório com senha em um servidor web?

Basic Auth bloqueia o acesso HTTP a um diretório em poucos minutos, sem código aplicacional nem banco de dados para gerenciar.

Os casos de uso se repetem o tempo todo: um ambiente de pré-produção em um subdomínio do tipo preprod.captaindns.com, um back-office interno sem autenticação própria, uma documentação técnica que não deveria estar pública, uma ferramenta de monitoramento exposta por engano durante uma implantação às pressas. Em todos esses casos, ninguém tem tempo nem necessidade de programar um sistema de contas completo. Uma camada HTTP é mais do que suficiente.

A armadilha clássica: acreditar que um diretório não linkado a partir do site permanece invisível. Um robots.txt que exclui /preprod/ só impede o rastreamento dos mecanismos de busca que respeitam o padrão, não o acesso direto. Um scanner automatizado, um link compartilhado por engano em um ticket, uma entrada de log que vaza em algum lugar, e a URL circula. Existem ferramentas inteiras dedicadas a escanear sistematicamente caminhos comuns (staging, dev, admin, backup) em faixas de IP completas. Nada de paranoia nisso: é ruído de fundo permanente na Internet.

Uma rápida inspeção nos logs de acesso de um servidor exposto à Internet é suficiente para se convencer disso. Requisições para /admin/, /wp-admin/, /.env, /backup.zip ou /phpinfo.php chegam continuamente, minuto após minuto, bem antes que qualquer humano tenha tido tempo de encontrar a URL por outro meio. Esses scans não miram ninguém em particular: eles varrem faixas de IP inteiras em busca de caminhos conhecidos por estarem mal protegidos. Um diretório de pré-produção com nome previsível, /staging/ ou /preprod/, acaba aparecendo nesses logs mais cedo ou mais tarde.

Basic Auth HTTP não é uma autenticação aplicacional completa. Não há logout adequado: o navegador mantém as credenciais em memória enquanto não for fechado. Não há limitação do número de tentativas. Não há sessão com expiração, nem gestão refinada de papéis. É uma fechadura na porta, não um controle de acesso sofisticado. Para um acesso que exija papéis diferenciados ou rastreabilidade detalhada, é necessária uma camada aplicacional dedicada. Para bloquear rapidamente um acesso sensível enquanto se espera algo melhor, ou como complemento de uma proteção já existente, Basic Auth cumpre perfeitamente seu papel.

As alternativas existem, e muitas vezes são mais robustas no papel. Uma VPN filtra o acesso de rede antes mesmo que qualquer requisição HTTP chegue ao servidor. Um SSO corporativo, Okta, Google Workspace ou Azure AD, centraliza as contas e o registro de conexões. Um proxy reverso com OAuth2, oauth2-proxy ou Authelia por exemplo, adiciona uma sessão aplicacional de verdade com expiração e logout adequado. Mas essas soluções exigem uma infraestrutura completa: um servidor VPN para manter, um provedor de identidade para integrar, um proxy adicional para implantar e monitorar. Basic Auth se configura em poucos minutos com o que já está rodando no servidor, sem dependência externa nem conta para provisionar em outro lugar. Não é a mais elegante no papel. Mas ela bloqueia de fato um acesso antes do fim do dia.

Basic Auth e Digest Auth: como funciona a autenticação HTTP?

O protocolo HTTP inclui desde sempre um mecanismo de autenticação baseado em cabeçalhos, sem cookie nem sessão aplicacional.

A troca acontece em dois tempos. O navegador solicita um recurso protegido, o servidor responde 401 Unauthorized com um cabeçalho WWW-Authenticate que especifica o esquema esperado e o nome da zona protegida, o realm. O navegador então exibe sua caixa de diálogo nativa, o usuário digita suas credenciais, e o navegador reenvia a requisição com um cabeçalho Authorization. Enquanto a sessão do navegador permanecer aberta, esse cabeçalho é reenviado automaticamente a cada requisição para o mesmo realm, sem nova digitação.

Basic Auth: o mecanismo padrão (RFC 7617)

Basic Auth codifica o usuário e a senha em base64, sem nenhuma criptografia. O formato do cabeçalho Authorization é Basic seguido de base64(usuário:senha).

Ponto importante desde já: base64 não é criptografia. É uma simples codificação reversível, decodificável por qualquer pessoa com um comando.

$ echo -n "admin:senha" | base64
YWRtaW46c2VuaGE=

A RFC 7617 afirma sem rodeios: o esquema Basic não fornece nenhuma proteção de confidencialidade para as credenciais transmitidas, e seu uso em uma conexão não criptografada as expõe a qualquer um que intercepte o tráfego. É por isso que Basic Auth nunca deve rodar sobre HTTP puro.

Esse diálogo pode ser visto diretamente com curl. Uma primeira requisição sem credenciais recebe a recusa:

$ curl -i https://preprod.captaindns.com/
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Zona restrita"

Com as credenciais, a requisição é aceita:

$ curl -i -u admin:senha https://preprod.captaindns.com/
HTTP/1.1 200 OK

A opção -u do curl constrói sozinha o cabeçalho Authorization codificado em base64. É exatamente o que um navegador faz internamente após a digitação em sua caixa de diálogo.

Digest Auth: por que praticamente desapareceu

Digest Auth faz o hash da senha no lado do cliente antes do envio, em vez de transmiti-la em texto puro codificado.

O cálculo combina o usuário, o realm, a senha e um nonce, um valor aleatório de uso único fornecido pelo servidor, através de uma função de hash: historicamente MD5, com SHA-256 disponível desde a RFC 7616. Em teoria, essa abordagem protege as credenciais mesmo sem HTTPS, já que a senha em si nunca transita pela rede.

A troca se apoia em uma resposta do servidor muito mais rica do que Basic Auth:

WWW-Authenticate: Digest realm="Zona restrita",
    qop="auth", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
    opaque="5ccc069c403ebaf9f0171e9517f40e41"

O cliente então calcula HA1 = MD5(usuário:realm:senha), HA2 = MD5(método:URI), e depois uma resposta final MD5(HA1:nonce:nc:cnonce:qop:HA2). Essa complexidade de cálculo, repetida a cada requisição, é precisamente o que levou a maioria das implementações a preferir Basic Auth atrás de TLS em vez de Digest Auth sem TLS.

Na prática, Digest Auth praticamente desapareceu. Sua implementação é mais complexa no lado do cliente: gestão do nonce, do contador de requisições nc, do cnonce. O suporte permanece irregular em casos avançados, e ele apresenta problemas atrás de certos proxies e balanceadores de carga que não esperam esse tipo de diálogo. Principalmente, a generalização do HTTPS tornou sua principal vantagem obsoleta. Proteger uma senha em trânsito já não tem muito interesse quando toda a conexão já é criptografada de ponta a ponta. O Apache (mod_auth_digest) e o nginx através de módulos de terceiros ainda oferecem suporte, mas quase ninguém mais utiliza. O restante deste guia se concentra em Basic Auth, o padrão de fato atrás de HTTPS.

Breve história do hashing de senhas na web

O formato de hash usado pelo .htpasswd tem uma história que remonta aos primórdios do Unix, bem antes da existência da web.

crypt() do Unix e o limite de 8 caracteres do DES

A função crypt() do Unix, projetada no final dos anos 1970 por Robert Morris para a sétima edição do Unix, criptografa uma senha com uma variante do DES (Data Encryption Standard) repetida 25 vezes. Um salt de 12 bits, ou seja, 4.096 valores possíveis, codificado em 2 caracteres, impede a reutilização direta de uma tabela pré-calculada entre sistemas.

Problema: o DES trabalha com blocos de 56 bits úteis, o que limita a senha considerada a 8 caracteres. O nono caractere e os seguintes são ignorados, pura e simplesmente. Uma senha de 20 caracteres se comporta exatamente como seus 8 primeiros.

No final dos anos 1970, 25 iterações de DES representavam um custo computacional real. Nos anos 1990, a potência dos processadores de uso geral havia sido multiplicada por várias ordens de grandeza. Essa proteção já estava ultrapassada diante de um ataque de força bruta conduzido a partir de um simples PC.

Verificar esse comportamento requer apenas um comando, ainda disponível via openssl:

$ openssl passwd -crypt -salt ab SenhaMuitoLonga
abXXXXXXXXXXXX

Substituir SenhaMuitoLonga por qualquer string de mais de 8 caracteres produz rigorosamente o mesmo hash desde que os 8 primeiros caracteres permaneçam idênticos. A prova mais simples de que um algoritmo projetado em 1979 não tem mais lugar em um arquivo .htpasswd criado hoje.

Por que o Apache criou o APR1-MD5 com a versão 1.3?

O problema do Apache não era tanto a fraqueza do crypt(), mas sua portabilidade. A implementação do crypt() difere de um sistema para outro: a glibc do Linux, os BSD e o Windows não produzem os mesmos hashes para as mesmas entradas. Um arquivo .htpasswd gerado em uma máquina Linux podia se tornar ilegível uma vez implantado em um servidor BSD.

A solução veio do FreeBSD, onde Poul-Henning Kamp havia projetado em 1994 um formato de hash baseado em MD5, independente da implementação crypt() do sistema hospedeiro. O Apache retomou esse princípio para seu comando htpasswd, sob o nome apr1, disponível através da opção -m desde o Apache 1.3 em 1998. Resultado: o mesmo hash, produzido e verificado de forma idêntica independentemente do SO do servidor.

APR1-MD5 aplica 1.000 iterações de MD5 sobre a senha e um salt de 8 caracteres, com uma codificação final no alfabeto base64 próprio do formato crypt. Frente a um MD5 puro, uma única iteração, o ganho é real: mil vezes mais cálculo para testar uma senha. Mas MD5 continua sendo uma função rápida, projetada originalmente para integridade de dados e não para resistir a tentativas massivas. Uma GPU moderna calcula milhões de iterações MD5 por segundo. Resultado: APR1-MD5 permanece vulnerável a ataques de dicionário contra senhas fracas ou medianas.

A chegada do bcrypt, Provos e Mazières, 1999

bcrypt nasce um ano após a adoção do APR1-MD5 pelo Apache, com uma mudança de abordagem radical: em vez de fixar um número de iterações, por que não torná-lo ajustável ao longo do tempo?

Niels Provos e David Mazières publicam em 1999 "A Future-Adaptable Password Scheme" na conferência USENIX. Sua constatação: toda função de hash de custo fixo acaba sendo alcançada pelo hardware, mais cedo ou mais tarde. Sua resposta, bcrypt, deriva da fase de inicialização de chave da cifra Blowfish, projetada por Bruce Schneier em 1993, deliberadamente custosa em memória e em cálculo. O número de rodadas se ajusta através de um fator de custo, expresso como uma potência de dois: cada incremento dobra o trabalho exigido tanto do atacante quanto do servidor.

Essa concepção muda o jogo. Um algoritmo de iterações fixas precisa ser substituído quando se torna rápido demais para o hardware da época. O bcrypt, por sua vez, se ajusta: aumenta-se o fator de custo, e o hash acompanha o ritmo do progresso do hardware sem mudar de algoritmo nem quebrar a compatibilidade dos hashes já armazenados.

htpasswd -B produz bcrypt desde o Apache 2.4, lançado em 2012, e ele se tornou o padrão de fato para .htpasswd assim como para armazenamento de senhas em geral. Em projetos recentes, o Argon2, vencedor da Password Hashing Competition de 2015, agora compete com ele, especialmente por sua resistência reforçada a circuitos dedicados (ASIC). O Argon2 permanece fora do escopo do .htpasswd: nem Apache nem nginx oferecem suporte nativo a ele para autenticação HTTP.

O artigo original de 1999 recomendava um fator de custo padrão de 6, considerado razoável para o hardware da época. O valor padrão do htpasswd -B hoje é 10, e subir para 12 para um acesso sensível continua sendo comum. É exatamente o mecanismo que Provos e Mazières haviam antecipado: o número em si não tem importância alguma, só importa a possibilidade de aumentá-lo sem mudar de algoritmo nem quebrar os hashes já armazenados.

Configurar a proteção por senha com Apache (.htaccess e .htpasswd)

No Apache, dois arquivos são suficientes: .htpasswd para as contas, .htaccess ou um bloco <Directory> para a declaração.

Criar o arquivo .htpasswd

O comando htpasswd, fornecido pelo pacote apache2-utils no Debian/Ubuntu ou httpd-tools no RHEL/CentOS, gerencia a criação e a atualização do arquivo.

# Criar o arquivo e adicionar uma primeira conta, em bcrypt
htpasswd -c -B /var/www/secrets/.htpasswd admin

# Adicionar uma segunda conta ao arquivo existente (sem -c, senão o arquivo é sobrescrito)
htpasswd -B /var/www/secrets/.htpasswd editor

A opção -c cria o arquivo. Use apenas uma vez: executá-la novamente apaga as contas já existentes. -B força o formato bcrypt. Sem ela, htpasswd recai em APR1-MD5 por padrão, ou aceita -d para o antigo crypt() DES, -s para SHA-1. Esses dois últimos formatos não têm mais lugar em um arquivo criado hoje.

Sem acesso SSH, ou sem apache2-utils no servidor? Um gerador online produz a mesma linha, diretamente no navegador, sem instalar nada.

No Apache, o módulo correspondente, mod_auth_basic, com mod_authn_file para leitura do arquivo, deve estar ativado:

sudo a2enmod auth_basic authn_file
sudo systemctl reload apache2

Em uma distribuição onde esses módulos não são carregados por padrão, a configuração abaixo falha silenciosamente ou retorna um erro 500 Internal Server Error, visível apenas no error.log.

Declarar a proteção: AuthType, AuthName, AuthUserFile, Require valid-user

Dois locais possíveis para essas diretivas: o arquivo .htaccess do diretório a ser protegido, ou um bloco <Directory> na configuração do VirtualHost.

No .htaccess, na raiz do diretório a proteger:

AuthType Basic
AuthName "Zona restrita"
AuthUserFile /var/www/secrets/.htpasswd
Require valid-user

O mesmo resultado, no VirtualHost, em bloco <Directory>:

<VirtualHost *:443>
    ServerName preprod.captaindns.com
    DocumentRoot /var/www/html/preprod

    <Directory "/var/www/html/preprod">
        AuthType Basic
        AuthName "Zona restrita"
        AuthUserFile /var/www/secrets/.htpasswd
        Require valid-user
    </Directory>
</VirtualHost>

As duas sintaxes são funcionalmente equivalentes, mas não em desempenho. Um .htaccess é relido pelo Apache a cada requisição, para cada diretório pai do caminho solicitado. O bloco <Directory>, por sua vez, é carregado uma única vez na inicialização do servidor. Em um site de alto tráfego, o bloco <Directory> evita uma leitura de disco repetida a cada requisição.

Require valid-user autoriza qualquer conta presente no arquivo .htpasswd, independentemente do nome. Para restringir o acesso a contas específicas, Require user admin editor substitui a linha.

A armadilha AllowOverride AuthConfig

Um .htaccess com diretivas de autenticação perfeitamente corretas, mas que não produz efeito algum. É o sintoma número um relatado nos fóruns de hospedagem e no StackOverflow.

A causa: a diretiva AllowOverride, definida no nível do VirtualHost ou da configuração global, controla quais categorias de diretivas um .htaccess tem permissão para modificar. Se estiver como None, o Apache simplesmente ignora o arquivo .htaccess. Sem erro, sem aviso visível nos logs aplicacionais. Apenas um diretório que permanece aberto.

<Directory "/var/www/html">
    AllowOverride AuthConfig
</Directory>

AuthConfig autoriza as diretivas relacionadas à autenticação (AuthType, AuthName, AuthUserFile, Require). All autoriza tudo, o que funciona mas abre mais do que o necessário. Após a modificação, basta recarregar:

sudo apachectl configtest && sudo systemctl reload apache2

configtest verifica a sintaxe antes de recarregar. Um simples teste evita derrubar um servidor em produção por um erro de digitação no arquivo de configuração.

Verificar se a proteção está funcionando

Após recarregar, uma requisição curl confirma a implantação antes de testar no navegador:

curl -i https://preprod.captaindns.com/preprod/
# Deve retornar 401 Unauthorized

curl -i -u admin:senha https://preprod.captaindns.com/preprod/
# Deve retornar 200 OK

Se o primeiro comando retornar diretamente 200, duas causas prováveis: AllowOverride que ignora o .htaccess, ou o bloco <Directory> mal direcionado para o caminho errado. O arquivo de log de erros, /var/log/apache2/error.log no Debian/Ubuntu, confirma a leitura ou não do arquivo AuthUserFile a cada tentativa.

Configurar a proteção por senha com nginx (auth_basic)

nginx protege um diretório com duas diretivas em um bloco location, sem nenhum arquivo .htaccess.

auth_basic e auth_basic_user_file

O módulo ngx_http_auth_basic_module é compilado por padrão no nginx. Um bloco location é suficiente:

server {
    listen 443 ssl;
    server_name preprod.captaindns.com;

    location /preprod/ {
        auth_basic           "Zona restrita";
        auth_basic_user_file /var/www/secrets/.htpasswd;
    }
}

auth_basic define o texto do realm, exibido na caixa de diálogo do navegador, ou off para desativar a autenticação em um subcaminho herdado de um location pai. auth_basic_user_file aponta para o mesmo formato de arquivo usado pelo Apache.

Este arquivo .htpasswd é diretamente compatível entre os dois servidores. Um par usuário/senha gerado no lado Apache funciona tal qual no nginx, e vice-versa. nginx lê o formato $apr1$, através de sua própria implementação embarcada de MD5 crypt, há muito tempo, e o formato bcrypt $2y$ desde o nginx 1.0.3, nos sistemas cuja crypt_r() do sistema suporta bcrypt, o que cobre as distribuições Linux comuns equipadas com libxcrypt.

nginx não lê .htaccess: uma diferença de arquitetura a entender

nginx nunca procura um arquivo .htaccess. Toda a configuração reside no nginx.conf e nos arquivos incluídos, carregados uma única vez na inicialização do serviço.

Essa escolha de arquitetura, assumida desde os primórdios do nginx, explica parte de sua reputação de desempenho. O Apache, com AllowOverride ativo, verifica a existência de um .htaccess em cada diretório pai do caminho solicitado, a cada requisição. O nginx não tem nada disso a fazer: toda a configuração já está carregada em memória, pronta para uso, antes mesmo da primeira requisição.

Consequência prática: copiar e colar uma configuração .htaccess do Apache em um diretório servido pelo nginx não faz absolutamente nada. O arquivo é ignorado silenciosamente, sem mensagem de erro. As diretivas equivalentes devem ser reescritas no bloco server ou location correspondente, e então aplicadas por um recarregamento:

sudo nginx -t && sudo systemctl reload nginx

Combinar com uma restrição de IP

nginx aceita allow e deny como complemento de auth_basic, no mesmo bloco location:

location /preprod/ {
    allow 203.0.113.0/24;
    deny  all;

    auth_basic           "Zona restrita";
    auth_basic_user_file /var/www/secrets/.htpasswd;
}

As duas camadas se combinam. Um IP fora da faixa autorizada recebe um 403 antes mesmo que o nginx solicite as credenciais. Um IP autorizado ainda assim deve se autenticar em seguida. Essa dupla barreira protege contra o roubo apenas da senha: um atacante que obtenha as credenciais mas não esteja na rede correta permanece bloqueado antes.

Basic Auth atrás de um proxy reverso: Traefik e Caddy

O formato .htpasswd não se limita ao Apache e nginx. Os proxies reversos modernos o utilizam tal qual, com uma restrição notável quanto ao formato de hash aceito dependendo do proxy.

Traefik lê diretamente um arquivo .htpasswd através de seu middleware basicAuth, declarado como label Docker ou em configuração estática:

labels:
  - "traefik.http.middlewares.preprod-auth.basicauth.usersfile=/etc/traefik/.htpasswd"
  - "traefik.http.routers.preprod.middlewares=preprod-auth"

O arquivo referenciado é o mesmo .htpasswd gerado para Apache ou nginx: APR1-MD5 e bcrypt funcionam ambos, sem conversão.

O Caddy, por sua vez, restringe a escolha ao mínimo estrito. Sua diretiva basic_auth só aceita o formato bcrypt, produzido por seu próprio comando caddy hash-password ou, de forma equivalente, por htpasswd -B:

caddy hash-password --plaintext SenhaMuitoLonga

Um arquivo .htpasswd herdado em APR1-MD5 falha silenciosamente atrás do Caddy: impossível autenticar enquanto as contas não forem regeneradas em bcrypt. Essa restrição confirma o que a história do formato já deixava antever: bcrypt é hoje o único formato que atravessa todos os servidores e proxies reversos web comuns, sem exceção.

Diagrama do diálogo HTTP Basic Auth: requisição inicial, resposta 401 com WWW-Authenticate, requisição reenviada com o cabeçalho Authorization, resposta 200

APR1-MD5 vs bcrypt: qual algoritmo escolher para .htpasswd?

Para um arquivo .htpasswd criado hoje, bcrypt é a escolha padrão. APR1-MD5 só se justifica por uma restrição de compatibilidade específica.

Funcionamento do APR1-MD5

O cálculo encadeia dois hashes MD5 intermediários, depois 1.000 iterações, antes de uma codificação específica.

Primeiro, dois hashes iniciais são calculados: um sobre a concatenação senha + $apr1$ + salt, o outro sobre senha + salt + senha. Em seguida, fragmentos do segundo hash são reinjetados no primeiro segundo um padrão que depende do comprimento da senha. Vem então o laço de 1.000 iterações: a cada rodada, senha, salt e hash da rodada anterior se recombinam em uma ordem que varia conforme a paridade do número da rodada. O resultado final, 16 bytes, é relido em uma ordem entrelaçada e então codificado no alfabeto base64 próprio do crypt (./0-9A-Za-z), para produzir os 22 caracteres do hash.

São essas 1.000 iterações que distinguem APR1-MD5 de um MD5 puro: elas multiplicam por mil o custo de uma tentativa. Mil é irrisório diante de uma GPU moderna, capaz de testar vários bilhões de combinações MD5 por segundo.

Funcionamento do bcrypt

bcrypt deriva da fase de configuração de chave, ou key setup, da cifra Blowfish, deliberadamente custosa de calcular.

Essa fase, chamada EksBlowfish (Expensive Key Schedule Blowfish), mistura senha e salt nas subchaves do Blowfish através de um número de rodadas igual a 2 elevado ao fator de custo escolhido. Diferentemente do MD5, essa etapa solicita acessos de memória não sequenciais que limitam fortemente a paralelização em GPU ou circuito dedicado: cada unidade de cálculo deve acessar uma tabela de grande tamanho de forma imprevisível, um padrão que o hardware massivamente paralelo gerencia mal. O salt, 128 bits, é integrado diretamente no hash final, ao contrário do APR1-MD5 que o armazena em um campo separado.

Com o fator de custo 10 adotado por padrão no htpasswd -B e nos geradores CaptainDNS, o cálculo gira em torno de 60 ms em um servidor genérico. Passar para 12 o leva a cerca de 250 ms. Esse tempo é pago a cada requisição autenticada, não apenas na criação da conta: um fator muito elevado se sente imediatamente no uso.

Tabela comparativa estendida

FormatoPrefixoSaltCusto de cálculoResistência GPU/ASICCompatibilidade servidoresIntroduçãoVeredito
crypt() DESnenhum12 bits (2 car.)25 rodadas DES, senha truncada em 8 car.NulaApenas históricoFim dos anos 1970Obsoleto
SHA-1 {SHA}Nenhum1 iteraçãoNulaApache, nginxAnos 1990Evitar
APR1-MD5$apr1$8 car.1.000 iterações MD5FracaApache, nginx, TraefikApache 1.3, 1998Compatibilidade máxima
bcrypt$2y$128 bits2^N rodadas, ajustávelElevadaApache 2.4+, nginx recente, Traefik, Caddy1999 (USENIX)Recomendado

O abismo entre as duas últimas linhas não é apenas uma questão de geração. APR1-MD5 tem um custo fixado de uma vez por todas em 1998. bcrypt se recalibra: passar um fator de custo de 10 para 12 hoje reproduz, para um atacante, uma dificuldade relativa comparável à do artigo de 1999, enquanto o hardware foi multiplicado nesse intervalo.

Verificar um hash offline

Os dois formatos podem ser controlados com os utilitários do sistema, sem gerador online:

# APR1-MD5, impondo o salt para comparar
openssl passwd -apr1 -salt Xq7nD2mR SenhaMuitoLonga

# bcrypt, custo 12
htpasswd -nbB -C 12 admin SenhaMuitoLonga

Para bcrypt, a comparação direta de dois hashes nunca funciona: o salt muda a cada cálculo, mesmo com senha e custo idênticos. A única verificação válida refaz o cálculo com o salt já presente no hash armazenado, o que a função crypt() do sistema faz nativamente no momento da autenticação.

Gráfico comparativo do tempo de cálculo por algoritmo de hash .htpasswd, escala logarítmica, APR1-MD5 versus bcrypt nos fatores de custo 8, 10, 12 e 14

Permissões de arquivos: complementar a proteção por senha

Basic Auth protege o acesso HTTP a um diretório. Ela não protege mais nada se o próprio arquivo .htpasswd estiver mal posicionado ou mal protegido no lado do sistema de arquivos.

Localização do arquivo .htpasswd, fora do DocumentRoot

O arquivo .htpasswd nunca deve ficar em um diretório servido diretamente pelo servidor web.

/var/www/html/          <- DocumentRoot, servido por HTTP
/var/www/secrets/       <- fora do DocumentRoot, nunca servido
    .htpasswd

Um arquivo .htpasswd colocado por engano em /var/www/html/.htpasswd se torna, salvo bloqueio explícito dos arquivos que começam com ponto, baixável através de uma simples requisição HTTP. Um atacante então obtém todos os hashes do arquivo e os ataca offline, tranquilamente, sem que o fator de custo do servidor o diminua minimamente. O cálculo acontece na máquina dele, não na sua.

Permissões recomendadas (chmod, chown, umask)

Dois níveis de permissões importam: o arquivo .htpasswd em si, e o diretório protegido.

Para .htpasswd, buscar 640 (leitura/escrita para o proprietário, leitura para o grupo, nada para os outros), com um proprietário coerente com o usuário do servidor web, www-data no Debian/Ubuntu, apache no RHEL, ou seu grupo:

chown root:www-data /var/www/secrets/.htpasswd
chmod 640 /var/www/secrets/.htpasswd

644 continua sendo uma armadilha clássica em diagnóstico rápido: esse modo torna o arquivo legível por todos os usuários do sistema, não apenas pelo processo Apache ou nginx. Em um servidor compartilhado ou multiusuário, qualquer conta local pode então ler os hashes. O mesmo reflexo se aplica ao diretório protegido: 750 é mais do que suficiente, proprietário e grupo apenas, nada para os outros. 777 em diagnóstico rápido ainda aparece com frequência, e nunca resolveu nada de forma duradoura.

Uma palavra sobre umask, frequentemente esquecido: se o arquivo .htpasswd for recriado por um script de implantação em vez de manualmente com htpasswd, o valor de umask do processo que o escreve determina suas permissões padrão. Um umask muito permissivo, 022 ou menos restritivo ainda, pode recriar um arquivo legível por todos a cada implantação, silenciosamente, sobrescrevendo o chmod 640 aplicado manualmente na vez anterior. Verificar as permissões após cada implantação automatizada evita a regressão.

Último lembrete útil: Basic Auth protege o acesso HTTP, não o acesso ao sistema de arquivos. Um chmod errado contorna toda a camada aplicacional, em silêncio, sem que nada nos logs HTTP sinalize.

Boas práticas e armadilhas a evitar

Basic Auth mal configurada dá uma falsa impressão de segurança. Algumas regras simples evitam a maioria dos pontos cegos.

HTTPS não é negociável. A RFC 7617 afirma: o esquema Basic não criptografa nada. Sem TLS, a senha codificada em base64 pode ser lida em texto puro por qualquer um que intercepte o tráfego, em um Wi-Fi público, um proxy comprometido ou uma simples ferramenta de captura de pacotes. Ativar Basic Auth sobre HTTP puro equivale a exibir a senha em um cartaz.

Nunca reutilize uma senha aplicacional ou pessoal para uma conta .htpasswd. Esses arquivos costumam ser menos monitorados do que os sistemas de autenticação principais, e uma senha compartilhada entre dois sistemas multiplica a superfície de ataque em caso de vazamento de um deles.

Combine com uma restrição de IP quando o público-alvo for conhecido previamente. No Apache, Require ip 203.0.113.0/24 em complemento de Require valid-user, via RequireAny. No nginx, allow e deny declarados antes da diretiva auth_basic. Duas camadas são melhores do que uma para um acesso realmente sensível.

Uma conta compartilhada por todo um time não rastreia nada. Se três pessoas se conectam com o mesmo usuário admin, impossível saber quem fez o quê em caso de incidente. Uma conta por pessoa custa trinta segundos a mais de configuração, e muda tudo no dia em que for preciso entender o que aconteceu.

O realm, o texto declarado em AuthName ou auth_basic, não é apenas um detalhe cosmético. Um título vago como "Zona restrita" evita revelar a natureza do serviço protegido a quem encontrar o prompt sem ter sido convidado. Nomear o realm "Painel admin Odoo" ou "Grafana interno" entrega gratuitamente uma informação a qualquer um que teste URLs aleatoriamente.

Alterar uma senha no .htpasswd não é suficiente para desconectar um navegador já autenticado. A credencial permanece memorizada no lado do cliente enquanto o navegador não receber um novo 401, e Basic Auth não tem nenhum mecanismo de logout para provocar um. Dois métodos forçam um novo prompt sem intervenção no lado do cliente: alterar o realm, o texto declarado em AuthName ou auth_basic, cria uma zona distinta aos olhos do navegador, que deixa de reenviar a credencial antiga e solicita novamente a digitação. Caso contrário, fechar o navegador ou abrir a URL em uma janela de navegação anônima continua sendo a única forma confiável de limpar uma credencial Basic Auth já memorizada.

Os logs de acesso padrão nunca registram a senha em texto puro: o cabeçalho Authorization não aparece no formato de log padrão do Apache ou do nginx. É um comportamento seguro por padrão, mas vale a pena verificá-lo explicitamente se um formato de log personalizado tiver sido adicionado em algum lugar da configuração. Um %i mal posicionado em um LogFormat do Apache escreveria o par usuário/senha codificado em base64 em um arquivo de log, potencialmente menos protegido do que o próprio arquivo .htpasswd.

Em uma infraestrutura com vários ambientes, dev, staging, pré-produção, gerenciar os arquivos .htpasswd manualmente logo vira uma bagunça: uma conta esquecida após a saída de um colega, uma senha que ainda está em APR1-MD5 enquanto o resto já migrou para bcrypt. Um playbook Ansible ou um script de implantação que regenere o arquivo a partir de uma fonte única, um arquivo listando as contas autorizadas por exemplo, evita a deriva entre ambientes e fornece um ponto único para remover um acesso.

Basic Auth continua sendo uma fechadura HTTP, não um sistema de autenticação aplicacional. Não há logout adequado: fechar o navegador ou mudar de URL continua sendo a única forma de se livrar dela no lado do cliente. Não há bloqueio após várias falhas, não há rotação automática de senhas. Para um acesso que tende a durar e a receber vários usuários com permissões diferentes, planeje uma migração para uma camada aplicacional dedicada assim que possível.

Plano de ação recomendado

  1. Escolher o servidor envolvido: Apache (.htaccess/.htpasswd) ou nginx (auth_basic), conforme a infraestrutura existente.
  2. Gerar um hash bcrypt para cada conta, ou APR1-MD5 somente se uma restrição de compatibilidade antiga o impuser.
  3. Posicionar o arquivo .htpasswd fora da raiz web, com permissões 640.
  4. Verificar se o site está em HTTPS antes de ativar Basic Auth: nunca sobre HTTP puro.
  5. Testar com uma conta real, depois documentar as credenciais em um gerenciador de senhas do time, não em um arquivo de texto compartilhado.

Gerar seus hashes .htpasswd sem linha de comando

Sem o pacote apache2-utils instalado, sem acesso SSH, ou simplesmente para ir mais rápido: os dois geradores CaptainDNS produzem diretamente a linha a ser colada no .htpasswd.

Gere seu hash .htpasswd

FAQ

O que é um arquivo .htaccess?

Um arquivo de configuração lido pelo Apache em cada diretório onde se encontra, a menos que uma diretiva AllowOverride None o tenha desativado no nível do servidor. Ele declara regras locais, incluindo autenticação, sem alterar a configuração global do VirtualHost. O nginx nunca o lê: toda sua configuração reside no nginx.conf.

Onde colocar o arquivo .htpasswd no servidor?

Fora da raiz web (DocumentRoot para Apache, root para nginx), por exemplo em /var/www/secrets/. Um arquivo .htpasswd acessível por HTTP pode ser baixado, e seus hashes atacados offline, sem que o fator de custo do bcrypt reduza minimamente a velocidade do atacante.

O .htpasswd ainda é seguro hoje?

Sim, desde que se use bcrypt (htpasswd -B) em vez de APR1-MD5 ou do antigo crypt() DES, e que o arquivo seja posicionado fora da raiz web com permissões restritivas, 640. O mecanismo Basic Auth em si permanece seguro, desde que rode atrás de HTTPS.

Como proteger o acesso a um diretório com Apache ou nginx?

No Apache, um arquivo .htpasswd e as diretivas AuthType, AuthName, AuthUserFile, Require valid-user, em um .htaccess ou bloco Directory. No nginx, as diretivas auth_basic e auth_basic_user_file em um bloco location, com o mesmo formato de arquivo .htpasswd. Em ambos os casos, HTTPS é indispensável.

O APR1-MD5 ainda é seguro em 2026?

Continua aceitável para compatibilidade retroativa, mas resiste consideravelmente menos do que bcrypt diante de um ataque offline conduzido em hardware GPU. Para um novo arquivo .htpasswd, prefira bcrypt (htpasswd -B), salvo restrição técnica específica que imponha APR1-MD5.

Como gerar um hash bcrypt para .htpasswd?

Com htpasswd -B, do pacote apache2-utils ou httpd-tools, em linha de comando, ou através de um gerador online quando o comando não estiver disponível. O resultado, no formato $2y$, é colado diretamente no arquivo .htpasswd, independentemente do servidor web utilizado.

Qual é a diferença entre auth_basic (nginx) e AuthType Basic (Apache)?

O protocolo HTTP subjacente é idêntico: ambos implementam o mesmo esquema Basic da RFC 7617 e leem o mesmo formato de arquivo .htpasswd. A diferença está na declaração: o Apache aceita .htaccess ou um bloco Directory, o nginx apenas diretivas em um bloco location de sua configuração centralizada.

Basic Auth é suficiente para proteger um diretório sensível?

Para bloquear o acesso anônimo e fechar uma URL adivinhada ou indexada por engano, sim. Para um acesso que exija uma verdadeira gestão de contas, logout, expiração de sessão ou logging detalhado, não. Basic Auth não possui nenhum desses mecanismos, e deve então ser complementada ou substituída por uma autenticação aplicacional.

Basic Auth funciona atrás de um CDN como o Cloudflare?

Sim, desde que a requisição chegue de fato ao servidor de origem sem ser servida a partir do cache antes do cabeçalho Authorization. Um registro proxied deixa passar Basic Auth por padrão, mas uma regra de cache mal direcionada no caminho protegido pode retornar uma resposta já em cache a um cliente não autenticado. Verifique se nenhuma regra de cache se aplica ao diretório protegido antes de considerar a proteção como confiável.

Baixe as tabelas comparativas

Assistentes conseguem reutilizar os números consultando os arquivos JSON ou CSV abaixo.

Glossário

  • Basic Auth: esquema de autenticação HTTP que transmite um usuário e uma senha codificados em base64 no cabeçalho Authorization. Definido pela RFC 7617.
  • Digest Auth: esquema de autenticação HTTP que faz o hash da senha no lado do cliente antes da transmissão, em vez de codificá-la em texto puro. Definido pela RFC 7616, muito pouco utilizado na prática.
  • .htpasswd: arquivo de texto que lista contas no formato usuário:hash, lido pelo Apache e nginx para verificar uma autenticação Basic.
  • .htaccess: arquivo de configuração do Apache, lido diretório por diretório, que pode declarar uma proteção por senha se AllowOverride o autorizar.
  • Sal (salt): valor aleatório adicionado à senha antes do hash, armazenado em texto puro ao lado do hash. Impede a reutilização de uma tabela pré-calculada entre várias contas ou vários sistemas.
  • Fator de custo: parâmetro do bcrypt que define o número de rodadas de cálculo, como potência de dois. Cada incremento dobra o tempo de cálculo necessário.
  • crypt(): família de funções Unix históricas dedicadas ao hashing de senhas, da qual derivam tanto o formato DES original quanto o APR1-MD5.
  • Rainbow table: tabela pré-calculada que associa hashes a senhas prováveis, utilizada para encontrar rapidamente uma senha a partir de seu hash não salgado.
  • AllowOverride: diretiva Apache que define quais categorias de diretivas um arquivo .htaccess tem permissão para modificar. AllowOverride None desativa totalmente a leitura do .htaccess.

Fontes

Artigos relacionados