Ir para o conteúdo principal

Proteger uma pasta 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 palavra-passe
TL;DR
  • O Basic Auth HTTP protege uma pasta através de um ficheiro .htpasswd (Apache) ou auth_basic_user_file (nginx), em poucos minutos de configuração.
  • A palavra-passe nunca é armazenada em texto simples: é hasheada, em APR1-MD5 (histórico, compatível em todo o lado) ou em bcrypt (recomendado, custo de cálculo ajustável).
  • Sem HTTPS, o Basic Auth transita em texto simples: ativar apenas com TLS.
  • O ficheiro .htpasswd deve residir fora da raiz web, nunca numa pasta que possa ser descarregada.
  • Gere os seus hash sem linha de comandos com os geradores APR1-MD5 e bcrypt do CaptainDNS.

Um servidor de pré-produção deixado indexado pelo Google. Um painel de monitorização acessível a quem adivinhar o URL. Documentação técnica publicada por engano em texto simples na web. Três cenários comuns, e uma única solução de primeiros socorros nos três casos: Basic Auth.

O Basic Auth existe desde os primórdios da web e quase não mudou desde então. Protege uma pasta ou um ficheiro com um simples par utilizador/palavra-passe, sem base de dados, sem sessão, sem dependência aplicacional. No Apache, bastam um ficheiro .htaccess e um ficheiro .htpasswd. No nginx, duas diretivas, auth_basic e auth_basic_user_file. Cinco minutos de configuração, e o acesso fica fechado.

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

Este conteúdo destina-se a administradores de sistemas, DevOps e programadores backend que gerem o seu próprio alojamento, e que pretendem fechar um acesso sem esperar pela implementação de um SSO ou de uma VPN.

Porquê proteger uma pasta com palavra-passe num servidor web?

O Basic Auth fecha o acesso HTTP a uma pasta em poucos minutos, sem código aplicacional nem base de dados para gerir.

Os casos de uso são recorrentes: um ambiente de pré-produção num subdomínio do tipo preprod.captaindns.com, um back-office interno sem autenticação própria, documentação técnica que não tem nada que estar pública, uma ferramenta de monitorização exposta por engano durante uma implantação à pressa. Em todos estes 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 uma pasta sem ligações a partir do site permanece invisível. Um robots.txt que exclua /preprod/ apenas impede o rastreio dos motores de busca que respeitam a norma, não o acesso direto. Um scanner automatizado, um link partilhado por engano num ticket, uma entrada de log que vaza algures, e o URL circula. Existem ferramentas inteiras para varrer sistematicamente caminhos comuns (staging, dev, admin, backup) em intervalos IP completos. Nada de paranoico nisto: é ruído de fundo permanente na Internet.

Uma inspeção rápida aos logs de acesso de um servidor exposto na Internet basta para nos convencer. Pedidos para /admin/, /wp-admin/, /.env, /backup.zip ou /phpinfo.php chegam continuamente, minuto após minuto, muito antes de um humano ter tido tempo de encontrar o URL por outro meio. Estes scans não visam ninguém em particular: varrem intervalos IP inteiros à procura de caminhos conhecidos por estarem mal protegidos. Uma pasta de pré-produção com um nome previsível, /staging/ ou /preprod/, acaba mais cedo ou mais tarde nestes logs.

O Basic Auth HTTP não é uma autenticação aplicacional completa. Não existe logout limpo: 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 fina de perfis. É um fecho na porta, não um controlo de acesso elaborado. Para um acesso que exija perfis diferenciados ou rastreabilidade fina, é necessária uma camada aplicacional dedicada. Para fechar rapidamente um acesso sensível enquanto se espera por algo melhor, ou como complemento de uma proteção já existente, o Basic Auth cumpre perfeitamente.

As alternativas existem, e são frequentemente mais robustas no papel. Uma VPN filtra o acesso de rede antes mesmo de qualquer pedido HTTP chegar ao servidor. Um SSO empresarial, Okta, Google Workspace ou Azure AD, centraliza as contas e o registo das ligações. Um reverse proxy com OAuth2, oauth2-proxy ou Authelia, por exemplo, acrescenta uma verdadeira sessão aplicacional com expiração e logout limpo. Mas estas soluções exigem uma infraestrutura completa: um servidor VPN para manter, um fornecedor de identidade para integrar, um proxy adicional para implementar e monitorizar. O Basic Auth configura-se em poucos minutos com o que já está a correr no servidor, sem dependência externa nem conta para provisionar noutro local. Não é a solução mais elegante no papel. Mas fecha realmente 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 decorre em dois tempos. O navegador pede 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 exibe então a sua caixa de diálogo nativa, o utilizador introduz as suas credenciais, e o navegador repete o pedido com um cabeçalho Authorization. Enquanto a sessão do navegador permanecer aberta, este cabeçalho é reenviado automaticamente em cada pedido para o mesmo realm, sem nova introdução.

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

O Basic Auth codifica o utilizador e a palavra-passe em base64, sem qualquer encriptação. O formato do cabeçalho Authorization é Basic seguido de base64(utilizador:palavra-passe).

Ponto a reter desde já: base64 não é encriptação. É uma simples codificação reversível, decifrável por qualquer pessoa num único comando.

$ echo -n "admin:palavra-passe" | base64
YWRtaW46cGFsYXZyYS1wYXNzZQ==

A RFC 7617 afirma-o sem rodeios: o esquema Basic não fornece qualquer proteção de confidencialidade para as credenciais transmitidas, e a sua utilização numa ligação não encriptada expõe-nas a quem intercetar o tráfego. É por isso que o Basic Auth nunca deveria correr sobre HTTP simples.

Este diálogo observa-se diretamente com curl. Um primeiro pedido sem credenciais recebe a recusa:

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

Com as credenciais, o pedido passa:

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

A opção -u do curl constrói ela própria o cabeçalho Authorization codificado em base64. É exatamente o que um navegador faz nos bastidores após a introdução na sua caixa de diálogo.

Digest Auth: porque praticamente desapareceu

O Digest Auth hasheia a palavra-passe do lado do cliente antes do envio, em vez de a transmitir em texto simples codificado.

O cálculo combina o utilizador, o realm, a palavra-passe 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, esta abordagem protege as credenciais mesmo sem HTTPS, uma vez que a própria palavra-passe nunca transita na rede.

A troca baseia-se numa resposta do servidor muito mais rica do que o Basic Auth:

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

O cliente calcula então HA1 = MD5(utilizador:realm:palavra-passe), HA2 = MD5(método:URI), e depois uma resposta final MD5(HA1:nonce:nc:cnonce:qop:HA2). Esta complexidade de cálculo, repetida a cada pedido, é precisamente o que levou a maioria das implementações a preferir o Basic Auth com TLS em vez do Digest Auth sem TLS.

Na prática, o Digest Auth praticamente desapareceu. A sua implementação é mais complexa do lado do cliente: gestão do nonce, do contador de pedidos nc, do cnonce. O suporte continua desigual nos casos avançados, e coloca problemas atrás de certos proxys e load balancers que não esperam este tipo de diálogo. Sobretudo, a generalização do HTTPS tornou a sua principal vantagem obsoleta. Proteger uma palavra-passe em trânsito já não tem grande interesse quando toda a ligação já está encriptada de ponta a ponta. O Apache (mod_auth_digest) e o nginx através de módulos de terceiros ainda o suportam, mas quase ninguém o utiliza. O resto deste guia concentra-se no Basic Auth, a norma de facto com HTTPS.

Pequena história do hashing de palavras-passe na web

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

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

A função crypt() do Unix, concebida no final dos anos 1970 por Robert Morris para a sétima edição do Unix, encripta uma palavra-passe com uma variante do DES (Data Encryption Standard) repetida 25 vezes. Um sal 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 sobre blocos de 56 bits úteis, o que limita a palavra-passe considerada a 8 caracteres. O nono caractere e os seguintes são ignorados, pura e simplesmente. Uma palavra-passe de 20 caracteres comporta-se exatamente como os seus primeiros 8.

No final dos anos 1970, 25 iterações de DES representavam um custo de cálculo real. Nos anos 1990, a potência dos processadores de consumo tinha sido multiplicada por várias ordens de grandeza. Esta proteção já estava ultrapassada face a um ataque de força bruta conduzido a partir de um simples PC.

Verificar este comportamento requer apenas um comando, sempre disponível via openssl:

$ openssl passwd -crypt -salt ab PalavraPasseMuitoLonga
abXXXXXXXXXXXX

Substituir PalavraPasseMuitoLonga por qualquer cadeia com mais de 8 caracteres produz rigorosamente o mesmo hash desde que os primeiros 8 caracteres permaneçam idênticos. A prova mais simples de que um algoritmo concebido em 1979 já não tem lugar num ficheiro .htpasswd criado hoje.

Porque é 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 a sua portabilidade. A implementação do crypt() difere de sistema para sistema: a glibc do Linux, os BSD e o Windows não produzem os mesmos hash para as mesmas entradas. Um ficheiro .htpasswd gerado numa máquina Linux podia tornar-se ilegível depois de implantado num servidor BSD.

A solução veio do FreeBSD, onde Poul-Henning Kamp concebeu em 1994 um formato de hash baseado em MD5, independente da implementação crypt() do sistema anfitrião. O Apache retomou este princípio para o 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, qualquer que seja o SO do servidor.

O APR1-MD5 aplica 1 000 iterações de MD5 sobre a palavra-passe e um sal de 8 caracteres, com uma codificação final no alfabeto base64 próprio do formato crypt. Face a um MD5 simples, uma única iteração, o ganho é real: mil vezes mais cálculo para testar uma palavra-passe. Mas o MD5 continua a ser uma função rápida, concebida originalmente para integridade de dados e não para resistir a tentativas massivas. Um GPU moderno calcula milhões de iterações MD5 por segundo. Resultado, o APR1-MD5 continua vulnerável a ataques de dicionário sobre palavras-passe fracas ou médias.

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

O 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, mais vale torná-lo ajustável no tempo.

Niels Provos e David Mazières publicam em 1999 « A Future-Adaptable Password Scheme » na conferência USENIX. A sua constatação: qualquer função de hash de custo fixo acaba por ser ultrapassada pelo hardware, mais cedo ou mais tarde. A sua resposta, o bcrypt, deriva da fase de inicialização da chave da cifra Blowfish, concebida por Bruce Schneier em 1993, propositadamente custosa em memória e em cálculo. O número de rondas ajusta-se através de um fator de custo, expresso como uma potência de dois: cada incremento duplica o trabalho exigido ao atacante e ao servidor.

Esta conceção muda o jogo. Um algoritmo de iterações fixas tem de ser substituído quando se torna demasiado rápido de calcular para o hardware da época. O bcrypt, por sua vez, ajusta-se: 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 hash já armazenados.

O htpasswd -B produz bcrypt desde o Apache 2.4, lançado em 2012, e tornou-se o padrão de facto para o .htpasswd tal como para o armazenamento de palavras-passe em geral. Nos projetos recentes, o Argon2, vencedor da Password Hashing Competition de 2015, concorre agora com ele, nomeadamente pela sua resistência reforçada aos circuitos dedicados (ASIC). O Argon2 permanece fora do âmbito do .htpasswd: nem o Apache nem o nginx o suportam nativamente 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 a ser comum. É exatamente o mecanismo que Provos e Mazières anteciparam: o número em si não tem importância nenhuma, só conta a possibilidade de o aumentar sem mudar de algoritmo nem quebrar os hash já armazenados.

Configurar a proteção por palavra-passe com o Apache (.htaccess e .htpasswd)

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

Criar o ficheiro .htpasswd

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

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

# Adicionar uma segunda conta ao ficheiro existente (sem -c, caso contrário o ficheiro é sobrescrito)
htpasswd -B /var/www/secrets/.htpasswd editor

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

Sem acesso SSH, ou sem o 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 o mod_authn_file para a leitura do ficheiro, deve estar ativado:

sudo a2enmod auth_basic authn_file
sudo systemctl reload apache2

Numa distribuição onde estes módulos não são carregados por padrão, a configuração abaixo falha silenciosamente ou devolve 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 estas diretivas: o ficheiro .htaccess da pasta a proteger, ou um bloco <Directory> na configuração do VirtualHost.

No .htaccess, na raiz da pasta a proteger:

AuthType Basic
AuthName "Zona reservada"
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 reservada"
        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 pedido, para cada pasta pai do caminho solicitado. O bloco <Directory>, por sua vez, é carregado uma única vez no arranque do servidor. Num site com muito tráfego, o bloco <Directory> evita uma leitura de disco repetida a cada pedido.

Require valid-user autoriza qualquer conta presente no ficheiro .htpasswd, independentemente do seu 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 qualquer efeito. É o sintoma número um reportado nos fóruns de alojamento e no StackOverflow.

A causa: a diretiva AllowOverride, definida ao nível do VirtualHost ou da configuração global, controla que categorias de diretivas um .htaccess tem o direito de modificar. Se estiver definida como None, o Apache ignora pura e simplesmente o ficheiro .htaccess. Sem erro, sem aviso visível nos logs aplicacionais. Apenas uma pasta que permanece aberta.

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

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

sudo apachectl configtest && sudo systemctl reload apache2

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

Verificar que a proteção funciona

Após recarregamento, um pedido curl confirma a implementação antes de testar no navegador:

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

curl -i -u admin:palavra-passe https://preprod.captaindns.com/preprod/
# Deve devolver 200 OK

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

Configurar a proteção por palavra-passe com o nginx (auth_basic)

O nginx protege uma pasta com duas diretivas num bloco location, sem qualquer ficheiro .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 reservada";
        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 num subcaminho herdado de um location pai. auth_basic_user_file aponta para o mesmo formato de ficheiro utilizado pelo Apache.

Este ficheiro .htpasswd é diretamente compatível entre os dois servidores. Um par utilizador/palavra-passe gerado no lado Apache funciona tal e qual no nginx, e vice-versa. O nginx lê o formato $apr1$, através da sua própria implementação embutida de MD5 crypt, desde há muito, 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.

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

O nginx nunca procura um ficheiro .htaccess. Toda a configuração reside no nginx.conf e nos ficheiros incluídos, carregados uma única vez no arranque do serviço.

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

Consequência prática: copiar-colar uma configuração .htaccess do Apache numa pasta servida pelo nginx não faz rigorosamente nada. O ficheiro é ignorado em silêncio, sem mensagem de erro. As diretivas equivalentes devem ser reescritas no bloco server ou location correspondente, e depois aplicadas por um recarregamento:

sudo nginx -t && sudo systemctl reload nginx

Combinar com uma restrição de IP

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

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

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

As duas camadas combinam-se. Um IP fora do intervalo autorizado recebe um 403 antes mesmo de o nginx pedir as credenciais. Um IP autorizado deve, ainda assim, autenticar-se em seguida. Esta dupla barreira protege contra o roubo apenas da palavra-passe: um atacante que obtenha as credenciais mas não esteja na rede correta fica bloqueado a montante.

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

O formato .htpasswd não se limita ao Apache e ao nginx. Os reverse proxies modernos retomam-no tal e qual, com uma restrição notável quanto ao formato de hash aceite consoante o proxy.

O Traefik lê diretamente um ficheiro .htpasswd através do seu middleware basicAuth, declarado em 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 ficheiro referenciado é o mesmo .htpasswd gerado para o Apache ou nginx: APR1-MD5 e bcrypt funcionam ambos, sem conversão.

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

caddy hash-password --plaintext PalavraPasseMuitoLonga

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

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

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

Para um ficheiro .htpasswd criado hoje, o bcrypt é a escolha padrão. O APR1-MD5 só se justifica por uma restrição de compatibilidade precisa.

Funcionamento do APR1-MD5

O cálculo encadeia dois hash MD5 intermédios, depois 1 000 iterações, antes de uma codificação específica.

Primeiro, dois hash iniciais são calculados: um sobre a concatenação palavra-passe + $apr1$ + sal, outro sobre palavra-passe + sal + palavra-passe. Em seguida, fragmentos do segundo hash são reinjetados no primeiro segundo um padrão que depende do comprimento da palavra-passe. Vem depois o ciclo de 1 000 iterações: a cada ronda, palavra-passe, sal e hash da ronda anterior recombinam-se numa ordem que varia segundo a paridade do número da ronda. O resultado final, 16 octetos, é relido numa ordem entrelaçada e depois codificado no alfabeto base64 próprio do crypt (./0-9A-Za-z), para produzir os 22 caracteres do hash.

São estas 1 000 iterações que distinguem o APR1-MD5 de um MD5 simples: multiplicam por mil o custo de uma tentativa. Mil é irrisório face a um GPU moderno, capaz de testar vários milhares de milhões de combinações MD5 por segundo.

Funcionamento do bcrypt

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

Esta fase, denominada EksBlowfish (Expensive Key Schedule Blowfish), mistura palavra-passe e sal nas subchaves do Blowfish através de um número de rondas igual a 2 elevado ao fator de custo escolhido. Ao contrário do MD5, esta etapa solicita acessos à memória não sequenciais que limitam fortemente a paralelização em GPU ou circuito dedicado: cada unidade de cálculo deve aceder a uma tabela de grande dimensão de forma imprevisível, um esquema que o hardware massivamente paralelo gere mal. O sal, 128 bits, é integrado diretamente no hash final, ao contrário do APR1-MD5 que o armazena num campo separado.

Com o fator de custo 10 adotado por padrão no htpasswd -B e nos geradores CaptainDNS, o cálculo ronda os 60 ms num servidor genérico. Passar para 12 leva-o para cerca de 250 ms. Este tempo é pago a cada pedido autenticado, não apenas na criação da conta: um fator demasiado elevado sente-se imediatamente na utilização.

Tabela comparativa alargada

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

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

Verificar um hash offline

Os dois formatos controlam-se com os utilitários do sistema, sem gerador online:

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

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

Para o bcrypt, a comparação direta de dois hash nunca funciona: o sal muda a cada cálculo, mesmo com palavra-passe e custo idênticos. A única verificação válida repete o cálculo com o sal 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 face ao bcrypt com fatores de custo 8, 10, 12 e 14

Permissões de ficheiros: completar a proteção por palavra-passe

O Basic Auth protege o acesso HTTP a uma pasta. Já não protege nada se o próprio ficheiro .htpasswd estiver mal colocado ou mal protegido ao nível do sistema de ficheiros.

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

O ficheiro .htpasswd nunca deve encontrar-se numa pasta servida diretamente pelo servidor web.

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

Um ficheiro .htpasswd colocado por engano em /var/www/html/.htpasswd torna-se, salvo bloqueio explícito dos ficheiros que começam por um ponto, descarregável num simples pedido HTTP. Um atacante recupera então todos os hash do ficheiro e ataca-os offline, tranquilamente, sem que o fator de custo do servidor o atrase minimamente. O cálculo é feito do lado dele, não do seu.

Permissões recomendadas (chmod, chown, umask)

Dois níveis de permissões contam: o ficheiro .htpasswd em si, e a pasta protegida.

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

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

644 continua a ser uma armadilha clássica em reparação rápida: este modo torna o ficheiro legível por todos os utilizadores do sistema, não apenas pelo processo Apache ou nginx. Num servidor partilhado ou multiutilizador, qualquer conta local pode então ler os hash. O mesmo reflexo aplica-se à pasta protegida: 750 é mais do que suficiente, proprietário e grupo apenas, nada para os outros. 777 em reparação rápida, ainda se vê demasiadas vezes, e nunca resolveu nada de forma duradoura.

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

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

Boas práticas e armadilhas a evitar

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

O HTTPS não é negociável. A RFC 7617 especifica-o: o esquema Basic não encripta nada. Sem TLS, a palavra-passe codificada em base64 lê-se em texto simples por quem intercetar o tráfego, num Wi-Fi público, num proxy comprometido ou numa simples ferramenta de captura de pacotes. Ativar o Basic Auth em HTTP simples equivale a exibir a palavra-passe num cartaz.

Nunca reutilizar uma palavra-passe aplicacional ou pessoal para uma conta .htpasswd. Estes ficheiros são frequentemente menos vigiados do que os sistemas de autenticação principais, e uma palavra-passe partilhada entre dois sistemas multiplica a superfície de ataque em caso de fuga de um deles.

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

Uma conta partilhada por toda uma equipa não regista nada. Se três pessoas se ligarem com o mesmo utilizador admin, impossível saber quem fez o quê em caso de incidente. Uma conta por pessoa custa trinta segundos extra de configuração, e muda tudo no dia em que for preciso compreender o que aconteceu.

O realm, o texto declarado em AuthName ou auth_basic, não é um mero detalhe cosmético. Um título vago como « Zona reservada » evita revelar a natureza do serviço protegido a quem se depare com o prompt sem ter sido convidado. Chamar ao realm « Painel admin Odoo » ou « Grafana interno » dá gratuitamente uma informação a quem testa URLs ao acaso.

Alterar uma palavra-passe no .htpasswd não basta para desligar um navegador já autenticado. A credencial permanece memorizada do lado do cliente enquanto o navegador não receber um novo 401, e o Basic Auth não tem qualquer mecanismo de logout para o provocar. Dois métodos forçam ainda assim um novo prompt sem intervenção do 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 volta a pedir a introdução. Caso contrário, fechar o navegador ou abrir o URL numa janela de navegação privada continua a ser a única forma fiável de esvaziar uma credencial Basic Auth já memorizada.

Os logs de acesso padrão nunca registam a palavra-passe em texto simples: 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 algures na configuração. Um %i mal colocado num LogFormat do Apache escreveria o par utilizador/palavra-passe codificado em base64 num ficheiro de log, potencialmente menos protegido do que o próprio ficheiro .htpasswd.

Numa infraestrutura com vários ambientes, dev, staging, pré-produção, gerir os ficheiros .htpasswd manualmente torna-se rapidamente uma confusão: uma conta esquecida após a saída de um colega, uma palavra-passe que ainda permanece em APR1-MD5 enquanto o resto já passou para bcrypt. Um playbook Ansible ou um script de implantação que regenere o ficheiro a partir de uma fonte única, um ficheiro com a lista das contas autorizadas, por exemplo, evita a deriva entre ambientes e fornece um ponto único onde remover um acesso.

O Basic Auth continua a ser um fecho HTTP, não um sistema de autenticação aplicacional. Não tem logout limpo: fechar o navegador ou mudar de URL continua a ser a única forma de o descartar do lado do cliente. Não tem bloqueio após várias falhas, não tem rotação automática de palavras-passe. Para um acesso que se destina a durar e a acolher vários utilizadores com permissões diferentes, prever uma migração para uma camada aplicacional dedicada assim que possível.

Plano de ação recomendado

  1. Escolher o servidor em causa: Apache (.htaccess/.htpasswd) ou nginx (auth_basic), conforme a infraestrutura existente.
  2. Gerar um hash bcrypt para cada conta, ou APR1-MD5 apenas se uma restrição de compatibilidade antiga o impuser.
  3. Colocar o ficheiro .htpasswd fora da raiz web, com permissões 640.
  4. Verificar que o site está em HTTPS antes de ativar o Basic Auth: nunca em HTTP simples.
  5. Testar com uma conta real, e depois documentar as credenciais num gestor de palavras-passe de equipa, não num ficheiro de texto partilhado.

Gerar os seus hash .htpasswd sem linha de comandos

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

Gere o seu hash .htpasswd

FAQ

O que é um ficheiro .htaccess?

Um ficheiro de configuração lido pelo Apache em cada pasta onde se encontra, salvo se uma diretiva AllowOverride None o tiver desativado ao nível do servidor. Declara regras locais, incluindo autenticação, sem mexer na configuração global do VirtualHost. O nginx nunca o lê: toda a sua configuração reside no nginx.conf.

Onde colocar o ficheiro .htpasswd no servidor?

Fora da raiz web (DocumentRoot para o Apache, root para o nginx), por exemplo em /var/www/secrets/. Um ficheiro .htpasswd acessível por HTTP pode ser descarregado, e os seus hash atacados offline, sem que o fator de custo do bcrypt atrase minimamente o 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 se coloque o ficheiro fora da raiz web com permissões restritivas, 640. O mecanismo Basic Auth em si continua seguro, desde que corra com HTTPS.

Como proteger o acesso a uma pasta com o Apache ou nginx?

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

O APR1-MD5 ainda é seguro em 2026?

Continua a ser aceitável para compatibilidade retroativa, mas resiste nitidamente menos bem do que o bcrypt face a um ataque offline conduzido em hardware GPU. Para um novo ficheiro .htpasswd, prefira o bcrypt (htpasswd -B), salvo restrição técnica precisa que imponha o APR1-MD5.

Como gerar um hash bcrypt para o .htpasswd?

Com htpasswd -B, do pacote apache2-utils ou httpd-tools, na linha de comandos, ou através de um gerador online quando o comando não está disponível. O resultado, no formato $2y$, cola-se diretamente no ficheiro .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 ficheiro .htpasswd. A diferença está na declaração: o Apache aceita .htaccess ou um bloco Directory, o nginx apenas diretivas num bloco location da sua configuração centralizada.

O Basic Auth é suficiente para proteger uma pasta sensível?

Para bloquear o acesso anónimo e fechar um URL adivinhado ou indexado por engano, sim. Para um acesso que exija uma verdadeira gestão de contas, um logout, uma expiração de sessão ou um registo detalhado, não. O Basic Auth não tem nenhum destes mecanismos, e deve então ser complementado ou substituído por uma autenticação aplicacional.

O Basic Auth funciona atrás de uma CDN como a Cloudflare?

Sim, desde que o pedido chegue efetivamente ao servidor de origem sem ser servido a partir da cache antes do cabeçalho Authorization. Um registo em proxy deixa passar o Basic Auth por padrão, mas uma regra de cache mal direcionada para o caminho protegido pode devolver uma resposta já em cache a um cliente não autenticado. Verificar que nenhuma regra de cache se aplica à pasta protegida antes de considerar a proteção como fiável.

Descarregue as tabelas comparativas

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

Glossário

  • Basic Auth: esquema de autenticação HTTP que transmite um utilizador e uma palavra-passe codificados em base64 no cabeçalho Authorization. Definido pela RFC 7617.
  • Digest Auth: esquema de autenticação HTTP que hasheia a palavra-passe do lado do cliente antes da transmissão, em vez de a codificar em texto simples. Definido pela RFC 7616, muito pouco utilizado na prática.
  • .htpasswd: ficheiro de texto que lista contas no formato utilizador:hash, lido pelo Apache e pelo nginx para verificar uma autenticação Basic.
  • .htaccess: ficheiro de configuração do Apache, lido pasta a pasta, que pode declarar uma proteção por palavra-passe se o AllowOverride o autorizar.
  • Sal (salt): valor aleatório adicionado à palavra-passe antes do hashing, armazenado em texto simples 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 fixa o número de rondas de cálculo, sob a forma de potência de dois. Cada incremento duplica o tempo de cálculo necessário.
  • crypt(): família de funções Unix históricas dedicadas ao hashing de palavras-passe, da qual derivam tanto o formato DES original como o APR1-MD5.
  • Rainbow table: tabela pré-calculada que associa hash a palavras-passe prováveis, utilizada para encontrar rapidamente uma palavra-passe a partir do seu hash não salgado.
  • AllowOverride: diretiva do Apache que define que categorias de diretivas um ficheiro .htaccess tem o direito de modificar. AllowOverride None desativa totalmente a leitura do .htaccess.

Fontes

Artigos relacionados