você está aqui: Home  → Arquivo de Mensagens

Protegendo senhas

Colaboração: André Milani

Data de Publicação: 22 de março de 2011

Neste post eu pretendo debater um pouco sobre a proteção no armazenamento de senhas de clientes e usuários de nossos sites em bancos de dados. Por mais que já estejamos carecas de saber que nossos sites devem prevenir ataques de SQL Injection (inserção de códigos SQL em campos de formulários ou parâmetros de request) e de outros tipos, não deixa de ser uma medida de prevenção proteger ainda mais as senhas cadastradas por nossos usuários em nossos sites, uma vez que temos usuários que utilizam uma senha padrão em vários serviços (obviamente não recomendado), e até mesmo para evitar que outra pessoa de posse da senha do usuário em nosso site possa realizar qualquer operação não autorizada pelo titular da conta.

Os códigos apresentados neste artigo são da linguagem SQL e alguns comandos da linguagem PHP, mas podem ser convertidos para a maioria das linguagens de programação. Para exemplificar o conteúdo abordado neste artigo, vamos considerar inicialmente que temos a seguinte tabela em nosso banco de dados:

ID USUARIO SENHA
1 André 123456
2 Milani abcdef

Mesmo supondo que nosso site seja seguro contra SQL Injection, todos que tiverem acesso ao banco de dados poderão ter acesso às senhas dos usuários. Isto inclui pelo menos os administradores do banco, e em alguns casos também os responsáveis pela hospedagem do site, até mesmo desenvolvedores e pessoas com acessos aos arquivos de backup do banco. Um dos métodos mais tradicionais para se proteger a senha dos usuários é por meio da geração de um código-hash.

Gerando um código-hash

Um código hash é o resultado de um algoritmo de criptografia que recebe como parâmetro um dado, que neste caso pode ser a senha do usuário. Algumas funções são conhecidas como one-way (via única), onde a partir do resultado não é possível realizar o caminho inverso e assim descobrir a senha cadastrada ou o dado passado como parâmetro. Alguns exemplos de algoritmos de hash one-way são o MD5 e o SHA1.

O algoritmo MD5 retorna uma string com 32 posições preenchidas com caracteres hexadecimais, que é formada a partir de uma fórmula própria.

  echo md5("123456");  // e10adc3949ba59abbe56e057f20f883e
  echo md5("abcdef");  // e80b5017098950fc58aad83c8c14978e

Já o algoritmoSHA1 retorna uma string com 40 posições, formada por uma fórmula própria e diferente do MD5.

  echo sha1("123456");  // 7c4a8d09ca3762af61e59520943dc26494f8941b
  echo sha1("abcdef");  // 1f8ac10f23c5b5bc1167bda84b833e5c057a77d2

Tanto o MD5 quanto o SHA1 retornam sempre o mesmo resultado para o mesmo parâmetro informado. Isto quer dizer que sempre que a função MD5 for chamada para o parâmetro 123456, o resultado será sempre e10adc3949ba59abbe56e057f20f883e. De posse do código-hash da senha do usuário no momento de seu cadastro, basta armazenar no banco o código-hash em questão, transformando nossa tabela USUARIOS fictícia para o seguinte formato:

  ID | USUARIO | SENHA
  1    André     e10adc3949ba59abbe56e057f20f883e
  2    Milani    e80b5017098950fc58aad83c8c14978e

Neste momento, mesmo com acesso (autorizado ou não-autorizado) nesta tabela, a senha dos usuários não fica tão exposta quanto anteriormente, diminuindo muito as chances de alguém que acesse esta tabela possa realizar alguma ação indevida com estes dados.

Autenticando a senha com o código-hash

Como fazer então para, no momento em que um usuário retornar ao site e desejar se autenticar, validar se a senha que ele informou no login é a mesma senha criptografada no banco? É simples: basta novamente gerar o código-hash da senha informada pelo usuário no momento da autenticação e verificar se os códigos hash são idênticos. Por exemplo:

  $usuarioLogin = "andre";
  $senhaLogin = "123456";
  $senhaLoginHash = md5(senhaLogin);
  
  $sql = "SELECT * FROM USUARIOS WHERE USUARIO = '".$usuarioLogin."' AND
  SENHA = '".$senhaLoginHash."'";

Se a consulta SQL apresentada retornar algum resultado, é porque temos um usuário autenticando-se com sucesso. Caso contrário, ou o usuário ou a senha foram informados de forma incorreta. Observe neste código que foi utilizada a função MD5 para abordar o exemplo. É importante salientar que é a mesma função de criptografia que deve ser utilizada no cadastro do usuário e no momento de autenticação, ou seja, se no cadastro for utilizado a função SHA1 para criptografar a senha, é a função SHA1 que deve ser utilizada no momento da autenticação.

Proteção contra ataques que força-bruta

Você já protegeu as senhas de seus clientes no banco de dados utilizando criptografia, porém o seu sistema ainda pode estar vulnerável contra ataques de força-bruta. Este tipo de ataque consiste em tentar milhares de combinações de senhas para um determinado usuário pré-conhecido. Por exemplo: um usuário mal-intencionado poderia, de posse do nome do usuário andre, criar um script que enviasse milhares de tentativas de senhas (ou de um dicionário de senhas), até obter sucesso (similar ao tipo de ataque que foi usado para descobrir a conta do Barack Obama no Twitter há um tempo atrás). Para evitar este tipo de ataque, basta você criar um contador de tentativas falhas de autenticação em sua tabela USUARIOS, deixando-a da seguinte forma:

  ID | USUARIO | SENHA                            | FALHAS
  1    André     e10adc3949ba59abbe56e057f20f883e   0
  2    Milani    e80b5017098950fc58aad83c8c14978e   0

Bastaria então incrementar o valor do campo FALHAS a cada tentativa inválida que for realizada para o usuário em questão. Uma regra em seu sistema pode definir que, a partir de três tentativas inválidas de autenticação para um usuário, o sistema bloqueie sua conta, não permitindo novas tentativas e solicitando que o usuário entre em contato para desbloquear a sua senha. Outra regra que poderia ser realizada é: ao atingir três senhas inválidas, o sistema não permite que o usuário em questão possa realizar novas tentativas de autenticação nos próximos 15 minutos, o que tornaria um ataque força-bruta bastante inviável.

Vale a pena comentar que o contador FALHAS deve ser zerado toda vez que o usuário em questão consiga se autenticar com sucesso, para evitar que, caso ele esqueça a senha de vez em quando, este contador bloqueie sua senha em um momento futuro sem que ele tenha errado a senha três vezes consecutivas.

André Milani é instrutor oficial do curso on-line de PHP & MySQL da Softblue onde possui um blog sobre PHP e bancos de dados http://www.softblue.com.br/site/info/n/sbfrom/v/Dicas-, formado em Ciência da Computação pela PUC-PR, pós- graduado em Business Intelligence pela mesma instituição e possui diversas certificações na área de TI. É também autor de vários livros na área de informática, entre eles o MySQL - Guia do Programador, PostgreSQL - Guia do Programador e Construindo Aplicações Web com PHP & MySQL, todos pela editora Novatec, os quais podem ser encontrados nas principais livrarias, inclusive no Submarino. Possui mais de 8 anos de experiência em desenvolvimento web, atuando há mais de 4 anos com cursos e treinamentos de profissionais.


Veja a relação completa dos artigos de André Milani

 

 

Opinião dos Leitores

Murilo Fujita
28 Mar 2011, 23:44
Gostei da publicação. Parabéns!
Em breve quero implementar esse procedimento.
Abraço e sucesso, Milani!
Fábio Ramon
22 Mar 2011, 13:40
Como citado no artigo, um dos objetivos é ocultar a senha de "todos que tiverem acesso ao banco de dados ... Isto inclui pelo menos os administradores do banco, e em alguns casos também os responsáveis pela hospedagem do site"

A solução apresentada, criptografar a senha do usuário através de uma função hash, contudo, não previne que alguém que tenha acesso ao banco de dados e que também tenha um usuário válido no sistema (seja, por exemplo, para fazer um teste de implantação) copie o hash da sua senha conhecida para o campo de outro usuário.

Esta falha permite que o malicioso se autentique com qualquer usuário usando sua senha.

Na minha época de programador, lá em 2003, eu costumava concatenar o usuário com a senha antes de gerar o hash.

$senhaLoginHash = md5(login . senhaLogin);

-- Fábio Ramon
Bruno Jesus
22 Mar 2011, 08:44
Como citado acima é perigoso usar o hash sozinho. Existem diversos sites que são especializados em encontrar o texto que gerou um determinado hash. Alguns MD5 de palavras comuns podem ser encontrados diretos no google.

Exemplo de site: http://md5crack.com/
Marcelo Malheiros
22 Mar 2011, 02:44
Olá, André! Obrigado pelo artigo! Escrevo para sugerir um pequeno aprimoramento: adicionar um valor aleatório ao cálculo do hash, para evitar ataques com Rainbow Tables:

http://en.wikipedia.org/wiki/Rainbow_table

O valor aleatório (chamado "salt") poderia ser armazenado na mesma tabela. A vantagem seria que, caso alguém tenha acesso ao banco de dados e obtenha o conjunto de hashes, fique bem mais difícil de computar as ditas rainbow tables para descobrir as senhas originais.

No caso, bastaria concatenar o salt à senha em claro antes de gerar o hash, durante o primeiro armazenamento e às posteriores conferências de login:

$senhaLoginHash = md5(senhaLogin . salt);

--mgm
*Nome:
Email:
Me notifique sobre novos comentários nessa página
Oculte meu email
*Texto:
 
  Para publicar seu comentário, digite o código contido na imagem acima
 


Powered by Scriptsmill Comments Script