Desenvolvimento - PHP

Encriptando senhas de forma segura

Veja neste artigo como encriptar suas senhas de uma forma segura e que dificulte a ação de hackers roubarem seus dados.

por Thiago Belem



Fala pessoal, tudo bom?

Hoje vamos falar um pouquinho sobre “Segurança”, e uma das coisas que acho mais interessante, nessa área, é a encriptação de senhas.

Esses dias li um artigo muito bom (bem antigo por sinal) no NetTuts+ e achei legal trazer algumas informações pra cá, de forma bem resumida e direta, porém recomendo muito a leitura do artigo original.

Hashing

Hashing consiste em proteger dados (strings, números), convertendo-os em um novo dado, geralmente menor e em formato de string ou inteiro.

Hashes geralmente são mão-única, o que significa que não há uma forma de reverter a encriptação, ou encontrar o dado original baseado no hash (resultado da encriptação).

O problema

Estamos acostumados a usar hashes como MD5 e SHA1 da seguinte forma:

<?php

// Senha do usuário, pode ter vindo do $_POST, $_GET ou outro lugar
$senha = 'olá mundo'; 

// Encripta a senha usando MD5
$senha = md5($senha);

// Resultado:
// ca4e913424bfcfe71c016829a371a1f1

// Salvamos essa senha encriptada no banco

No caso do MD5, resultado final é sempre uma string de 32 caracteres alfa-numéricos (128 bits).

Você pode usar o MD5 e pensar que está seguro, mas existe uma coisa chamada Rainbow Tables, onde um atacante gera uma tabela com o resultado da encriptação de todas as palavras de um dicionário, combinando palavras e até adicionando símbolos e dígitos à essas palavras…. Com essa Rainbow Table fica muito fácil (partindo do resultado final da encriptação) descobrir a senha original (olá mundo).

A solução simples: salts

A solução mais simples é utilizar um “salt” que é uma string complexa que será concatenada a toda e qualquer senha antes de encriptá-la, por exemplo:

<?php

$salt = '1%1cAu!g+>K53PY}';

// Senha do usuário, pode ter vindo do $_POST, $_GET ou outro lugar
$senha = 'olá mundo'; 

// Encripta a senha usando MD5
$senha = md5($senha . $salt);

// Resultado:
// c1de0ebde1fd59955ccd57ccd89ac2e9

// Salvamos essa senha encriptada no banco

Dessa forma, todas as senhas estarão mais protegidas… porém ainda temos um problema:

O problema: salt fixo

  • Todas as senhas usam o mesmo salt
  • O salt (que é fixo) está presente em algum arquivo/texto dentro do seu sistema
  • O invasor que conseguiu pegar o seu banco de dados (de senhas) também vai ter acesso aos arquivos e, consequentemente, ao salt
  • Com posse do salt o atacante gera uma Rainbow Table nova, usando aquele salt nas combinações.

Precisamos então - de alguma forma - proteger o salt, ou gerar um salt novo pra cada senha, o que seria o ideal.

A solução complicada: salts dinâmicos

Podemos gerar uma string aleatória no PHP de várias formas, mas a idéia principal aqui é: gerar uma string aleatória, utilizá-la como salt na hora de encriptar a senha do usuário e salvar AMBAS no banco de dados (a senha e a string utilizada como salt).

<?php

/**
 * Gera um salt aleatório
 *
 * @param int $tamanho Tamanho do salt
 *
 * @return string
 */
function geraSaltAleatorio($tamanho = 22) {
	return substr(sha1(mt_rand()), 0, $tamanho);  
}

$salt = geraSaltAleatorio();

// Senha do usuário, pode ter vindo do $_POST, $_GET ou outro lugar
$senha = 'olá mundo'; 

// Encripta a senha usando MD5
$senha = md5($senha . $salt);

// Resultado:
// c1de0ebde1fd59955ccd57ccd89ac2e9

// Salvamos $senha e $salt no banco de dados

Dessa forma, cada senha terá seu próprio salt e o atacante teria que gerar uma rainbow table pra cada salt, o que fica impraticável.

Mas infelizmente ainda temos um problema…

O problema: tempo

A maioria dos métodos de encriptação que conhecemos (como MD5 e SHA1) são criados para serem extremamente rápidos, pois são utilizados na verificação de integridade de arquivos… o que acaba sendo um tiro no pé quando estamos falando de segurança: quanto mais rápido o algoritmo mais fácil um ataque de força-bruta (com ou sem Rainbow Tables) pode conseguir encontrar a senha original.

Precisamos então trocar de algoritmo ou atrasar o nosso script…

A solução: atrasando o algoritmo

<?php

/**
 * Gera um salt aleatório
 *
 * @param int $tamanho Tamanho do salt
 *
 * @return string
 */
function geraSaltAleatorio($tamanho = 22) {
	return substr(sha1(mt_rand()), 0, $tamanho);  
}

$salt = geraSaltAleatorio();

// Senha do usuário, pode ter vindo do $_POST, $_GET ou outro lugar
$senha = 'olá mundo'; 

// Cria um hash
$hash = md5($senha . $salt);

// Encripta esse hash 1000 vezes
for ($i = 0; $i < 1000; $i++) {
	$hash = md5($hash);
}

// Salvamos $hash e $salt no banco de dados

Agora qualquer ataque de força-bruta irá demorar 1000x mais para conseguir chegar até sua senha original, o que é excelente!

Finalizando…

O artigo original não termina por aqui, ele sugere a utilização de um algoritmo chamado BLOWFISH que recebe um parâmetro onde você determina o “custo”, que está ligado à demora/ciclos de encriptação… quanto maior, mais demorado.

Espero que tenham entendido a idéia geral e tenham gostado!

Artigo originalmente publicado por Thiago Belem: Encriptando senhas de forma segura

Thiago Belem

Thiago Belem - Tenho 23 anos e trabalho com Desenvolvimento WEB há mais de 10 anos. Atualmente moro no Rio de Janeiro e, além de trabalhar como Freelancer, sou Professor no Assando Sites, meu curso online de CakePHP.