Autenticação e Criptografia de Senhas com PHP

Artigo que apresenta diferentes extensões do PHP relacionadas à criptografia/codificação de textos utilizando diferentes algorítmos.

Introdução
cadeado

É comum que sistemas de informações utilizem um mecanismo de autenticação para acesso às ferramentas restritas através de um login e senha. Por questões de confidencialidade e segurança, é comum, também, que as senhas fiquem guardadas de forma criptografada no banco de dados. Os algoritmos de criptografia usados para estes casos são aqueles de via única (unidirecional), ou seja, que não permitem que um valor codificado seja descriptografado facilmente. O nome dado a um valor criptografado também é chamado de "hash".

PHP oferece diferentes alternativas para criptografar uma senha. Cada uma delas pode ter suas peculiaridades na hora de se montar o mecanismo de autenticação. Vejamos algumas das principais funções usadas para criptografia de senhas:

  • md5 - Gera uma sequência binária de 16 bytes (ou 32 símbolos hexadecimais)
  • sha1 - Gera uma sequência binária de 20 bytes (ou 40 símbolos hexadecimais)
  • crypt - Gera um hash de acordo com um algum algoritmo escolhido (poucas opções)
  • hash - Módulo que oferece diferentes algoritmos de criptografia (para saber os disponíveis, basta usar hash_algos)
  • mcrypt - Módulo que oferece diferentes algoritmos de codificação/decodificação (para saber os disponíveis, basta usar mcrypt_list_algorithms)
  • mhash - Módulo obsoleto por hash.

MD5

Esta função é nativa do PHP e gera uma sequência binária de 16 bytes ou 32 símbolos hexadecimais. O uso é simples, basta passar o texto a ser codificado e se deseja obter o retorno na forma binária. Por padrão é retornado uma sequência hexadecimal, que é mais conveniente de se guardar em um banco de dados.

$hash_hex = md5('teste'); // Gera 698dc19d489c4e4db73e28a713eab07b
$hash_bin = md5('teste', true); // Gera 16 bytes (inclusive não imprimíveis)

Para testar se uma senha digitada é igual à senha criptografada no BD, basta criptografar a senha informada e comparar as duas criptografadas. Afinal, uma sequência sempre gera o mesmo resultado criptografado.

$senha_codificada_bd = $dados_bd['senha']; // exemplo de senha obtida do BD
$senha_codificada_informada = md5($_POST['senha']);

if ($senha_codificada_bd == $senha_codificada_informada) {
    // As senhas sao compativeis
}

Observação: Como o hash gerado por MD5 tem tamanho fixo, é óbvio que existem infinitas sequências textuais que produzem o mesmo hash. Porém, isso não é tão crítico, pois encontrar duas sequências que geram o mesmo hash é algo bastante incomum, especialmente entre senhas que envolvem apenas caracteres imprimíveis.


SHA1

Esta função é semelhante à MD5 (mesmos parâmetros), porém, o algoritmo gera uma sequência binária de 20 bytes ou 40 símbolos hexadecimais. Por padrão é retornado uma sequência hexadecimal.

$hash_hex = sha1('teste'); // Gera 2e6f9b0d5885b6010f9167787445617f553a735f
$hash_bin = sha1('teste', true); // Gera 20 bytes (inclusive não imprimíveis)

Para testar se uma senha digitada é igual à senha criptografada no BD, basta seguir a mesma ideia apresentada em MD5.


Crypt

A função crypt também é nativa do PHP, mas, diferente de md5 e sha1, é genérica, pois pode-se especificar qual algoritmo a ser usado. A função recebe uma string a ser codificada e, opcionalmente, um sal. O "sal" é uma sequência aleatória usada como base para codificar outra sequência. A ideia é que utilizando o mesmo sal para codificar o mesmo texto, obtenha-se o mesmo valor. Mas usando um sal diferente para codificar o mesmo texto, pode-se obter resultados diferentes.

A função crypt varia o seu funcionamento de acordo com o sal informado:

  • CRYPT_STD_DES - Sal de 2 caracteres alfa-numéricos;
  • CRYPT_EXT_DES - Sal de 9 caracteres, sendo: 1 underscore + 4 bytes (indicando o número de iterações) + 4 bytes alfa-numéricos (sal propriamente dito)
  • CRYPT_MD5 - Sal de 12 caracteres, sendo: iniciado em "$1$" + 8 bytes (sal) + final "$"
  • CRYPT_BLOWFISH - Sal iniciado em "$2a$" + 2 dígitos (custo) + símbolo "$" + 21 bytes alfa-numéricos ou "." ou "/" (sal) + final "$"
  • CRYPT_SHA256 - Sal de 16 caracteres, sendo: iniciado em "$5$" + 12 bytes (sal) + final "$"
  • CRYPT_SHA512 - Sal de 20 caracteres, sendo: iniciado em "$6$" + 16 bytes (sal) + final "$"

Nota: a partir do PHP 5.3.7, o PHP introduziu o prefixo "$2x$" e "$2y$" para o algoritmo CRIPT_BLOWFISH, que corrige uma falha de segurança relacionada ao blowfish. Portanto, servidores que utilizam esta versão ou posterior são recomendados a utilizar "$2y$".

Exemplo de uso:

$hash_md5 = crypt('teste', '$1$abcdefgh$');
// Gera $1$abcdefgh$DYdsp/CvdHfTi6XSCsPdg.

Observe que o valor codificado também tem como prefixo o próprio sal. Caso este valor codificado é usado como sal da função novamente, ela recupera apenas o sal, ignorando o valor codificado:

$hash_md5 = crypt('teste', '$1$abcdefgh$DYdsp/CvdHfTi6XSCsPdg.');
// Tambem gera $1$abcdefgh$DYdsp/CvdHfTi6XSCsPdg.

Portanto, para checar se uma senha codificada no BD é (provavelmente) a mesma que uma informada pelo usuário, basta fazer:

$senha_codificada_bd = $dados_bd['senha']; // exemplo de senha obtida do BD
$senha_codificada_informada = crypt($_POST['senha'], $senha_codificada_bd);

if ($senha_codificada_bd == $senha_codificada_informada) {
    // As senhas sao compativeis
}

Observação: em algumas versões antigas do PHP, esta função dependia dos recursos do sistema operacional. A partir da versão 5.3.0, o próprio PHP possui sua própria implementação destes recursos e não depende do sistema.


Hash

O módulo hash era um módulo pecl, mas passou a fazer parte do PHP na versão 5.1.2. Ele também é genérico, pois pode-se informar qual o algoritmo de codificação a ser usado. Para saber os algoritmos suportados, basta usar a função hash_algos, que devolve um array com os nomes dos algoritmos. Para usar a função hash, basta passar no primeiro parâmetro o nome do algoritmo a ser usado, no segundo o valor a ser codificado, e no terceiro o tipo de saída (binário ou hexadecimal).

Exemplo:

$hash_md5 = hash('md5', 'teste', false); // Gera 698dc19d489c4e4db73e28a713eab07b
$hash_md5 = hash('md5', 'teste', true); // Gera 16 bytes (inclusive não imprimíveis)

A vantagem deste módulo em relação aos demais é que o algoritmo é informado por parâmetro, logo, pode ser informado dinamicamente, com o valor armazenado em algum lugar.

Além de hash, existem outras funções no módulo, como a função hash_hmac, que recebe também uma chave de codificação (como um sal), e utiliza o algoritmo HMAC.

Exemplo:

$hash_md5 = hash_hmac('md5', 'teste', 'chave', false); // Gera d1c9c8e42bb681e024dd07ae91aeeb4a

Note que, diferente de crypt, a função hash_hmac não embute a chave (sal) no valor codificado, logo, ele não pode ser usado como parâmetro.

Alguns exemplos de algoritmos usados em hash: "md2", "md4", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "ripemd128", "ripemd160", "ripemd256", "ripemd320", "whirlpool", "tiger128,3", "tiger160,3", "tiger192,3", "tiger128,4", "tiger160,4", "tiger192,4", "snefru", "snefru256", "gost", "adler32", "crc32", "crc32b", "salsa10", "salsa20", "haval128,3", "haval160,3", "haval192,3", "haval224,3", "haval256,3", "haval128,4", "haval160,4", "haval192,4", "haval224,4", "haval256,4", "haval128,5", "haval160,5", "haval192,5", "haval224,5" e "haval256,5".


Mcrypt

O módulo mcrypt suporta vários mecanismos de codificação/decodificação. As principais funções são a mcrypt_generic e a mdcrypt_generic, que recebem um resource com o mecanismo de codificação/decodificação e o valor a ser codificado/decodificado. O resource é criado pela função mcrypt_module_open e manipulado por outras funções.

Um exemplo de uso pode ser visto no link: http://php.net/manual/en/function.mcrypt-module-open.php

Alguns exemplos de algoritmos suportados em mcrypt: "cast-128", "gost", "rijndael-128", "twofish", "arcfour", "cast-256", "loki97", "rijndael-192", "saferplus", "wake", "blowfish-compat", "des", "rijndael-256", "serpent", "xtea", "blowfish", "enigma", "rc2" e "tripledes".


Mhash

Este módulo está obsoleto pelo módulo hash. No PHP 5.3.0, as funções deste módulo são emuladas pelas funções do módulo hash. É melhor usar o módulo hash diretamente.

A principal função do módulo é mhash, que recebe o algoritmo, o valor a ser codificado e a chave HMAC (caso deseja-se utilizar o algoritmo HMAC). O valor retornado é hexadecimal.

Exemplo:

$hash_md5 = mhash(MHASH_MD5, 'teste'); // Gera 698dc19d489c4e4db73e28a713eab07b
$hash_md5_hmac = mhash(MHASH_MD5, 'teste', 'chave'); // Gera d1c9c8e42bb681e024dd07ae91aeeb4a

Alguns exemplos de algoritmos suportados em mhash: "ADLER32", "CRC32", "CRC32B", "GOST", "HAVAL128", "HAVAL160", "HAVAL192", "HAVAL224", "HAVAL256", "MD2", "MD4", "MD5", "RIPEMD128", "RIPEMD256", "RIPEMD320", "SHA1", "SHA192", "SHA224", "SHA256", "SHA384", "SHA512", "SNEFRU128", "SNEFRU256", "TIGER", "TIGER128", "TIGER160", "WHIRLPOOL".


Observações

Nos exemplos mostrados no artigo, utilizei o operador == para comparar as strings contendo hashes das senhas. No entanto, é interessante conhecer a existência de "Timing Attacks", que exploram, por exemplo, o comportamento deste operador no PHP para ajudar a descobrir senhas. Para mais informações, leia o artigo Prevenção de Timing Attack no PHP.

2 comentários

Junior disse...

legal seu blog,tenho uma duvida e to com problema com um codigo aqui de uma senha que esqueci pelo painel phpmyadmin nao consigo mudar a senha
senha: antiga 448cf94135ff734c4ed6a50180f759a37fc56270e7a70fa81a5935b72eacbe2992eb5ffee6ae2fec3ad71c777531578f

obrigado espero que ajude

Rubens Takiguti Ribeiro (autor do blog) disse...

Junior, esta senha parece estar codificada em uma sequência hexadecimal maior que aquela gerada por MD5. É praticamente impossível decodificar uma sequência como essa em tempo hábil.