Hashing de uma senha em Java
1. Visão geral
Neste tutorial, discutiremos a importância do hashing de senha.
Vamos dar uma olhada rápida no que é, por que é importante e algumas maneiras seguras e inseguras de fazer isso em Java.
2. O que é Hashing?
Hashing é o processo de gerar uma string, ouhash, de um determinadomessage usando uma função matemática conhecida comocryptographic hash function.
Embora existam várias funções de hash por aí, aquelas adaptadas para senhas de hash precisam ter quatro propriedades principais para garantir:
-
Deve serdeterministic: a mesma mensagem processada pela mesma função hash devealways espalhar o mesmohash
-
Não éreversible: é impraticável gerar ummessage a partir de seuhash
-
Tementropy alto: uma pequena mudança em amessage deve produzir umhash muito diferente
-
E resiste acollisions: doismessages diferentes não devem produzir o mesmohash
Uma função hash que possui todas as quatro propriedades é uma forte candidata ao hash de senha, pois juntos eles aumentam drasticamente a dificuldade de fazer engenharia reversa da senha a partir do hash.
Além disso,password hashing functions should be slow. Um algoritmo rápido ajudaria abrute force attacks em que um hacker tentaria adivinhar uma senha fazendo hash e comparando bilhões (or trillions) de possíveis senhas por segundo.
Algumas ótimas funções hash que atendem a todos esses critérios são PBKDF2, BCrypt, andSCrypt. Mas primeiro, vamos dar uma olhada em alguns algoritmos mais antigos e por que eles não são mais recomendados
3. Não recomendado: MD5
Nossa primeira função hash é o algoritmo MD5 de digestão de mensagens, desenvolvido em 1992.
MessageDigest do Java torna isso fácil de calcular e ainda pode ser útil em outras circunstâncias.
No entanto, nos últimos anos,MD5 was discovered to fail the fourth password hashing property in que se tornou computacionalmente fácil gerar colisões. Para completar, o MD5 é um algoritmo rápido e, portanto, inútil contra ataques de força bruta.
Por causa disso, MD5 não é recomendado.
4. Não recomendado: SHA-512
A seguir, veremos o SHA-512, que faz parte da família Secure Hash Algorithm, uma família que começou com o SHA-0 em 1993.
4.1. Por que SHA-512?
À medida que os computadores aumentam o poder e à medida que descobrimos novas vulnerabilidades, os pesquisadores obtêm novas versões do SHA. As versões mais recentes têm um comprimento progressivamente maior ou, às vezes, os pesquisadores publicam uma nova versão do algoritmo subjacente.
O SHA-512 representa a chave mais longa da terceira geração do algoritmo.
Enquantothere are now more secure versions of SHA,SHA-512 is the strongest that is implemented in Java.
4.2. Implementando em Java
Agora, vamos dar uma olhada na implementação do algoritmo de hashing SHA-512 em Java.
Primeiro, temos que entender o conceito desalt. Simplificando,this is a random sequence that is generated for each new hash.
Ao introduzir essa aleatoriedade, aumentamos o hashentropy e protegemos nosso banco de dados contra listas pré-compiladas de hashes conhecidas comorainbow tables.
Nossa nova função de hash se torna aproximadamente:
salt <- generate-salt;
hash <- salt + ':' + sha512(salt + password)
4.3. Gerando um Sal
Para introduzir o sal, usaremos oSecureRandom class dejava.security:
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
Em seguida, usaremos oMessageDigest class para configurar a funçãoSHA-512 hash com nosso sal:
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt);
E com isso adicionado, agora podemos usar o métododigest para gerar nossa senha com hash:
byte[] hashedPassword = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
4.4. Por que não é recomendado?
Quando empregado com sal,SHA-512 is still a fair option,but there are stronger and slower options out there.
Além disso, as opções restantes que cobriremos têm um recurso importante: força configurável.
5. PBKDF2, BCrypt e SCrypt
PBKDF2, BCrypt e SCrypt são três algoritmos recomendados.
5.1. Por que são recomendados?
Cada um deles é lento e cada um tem a brilhante característica de ter uma força configurável.
Isso significa que conforme os computadores aumentam em força,we can slow down the algorithm by changing the inputs.
5.2. Implementando PBKDF2 em Java
Agora,salts are a fundamental principle of password hashing, então precisamos de um para PBKDF2 também:
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
A seguir, criaremos umPBEKeySpec e umSecretKeyFactory que instanciaremos usando o salgoritmoPBKDF2WithHmacSHA1 :
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
O terceiro parâmetro (65536) é efetivamente o parâmetro de força. Indica quantas iterações são executadas por esse algoritmo, aumentando o tempo necessário para produzir o hash.
Finalmente, podemos usar nossoSecretKeyFactory para gerar o hash:
byte[] hash = factory.generateSecret(spec).getEncoded();
5.3. Implementando BCrypt e SCrypt em Java
Então, parece queBCrypt and SCrypt support don’t yet ship with Java, embora algumas bibliotecas Java os suportem.
Uma dessas bibliotecas é o Spring Security.
6. Hashing de senha com Spring Security
Embora o Java suporte nativamente os algoritmos de hash PBKDF2 e SHA, ele não oferece suporte aos algoritmos BCrypt e SCrypt.
Felizmente para nós, Spring Security vem com suporte para todos esses algoritmos recomendados por meio da interfacePasswordEncoder:
-
MessageDigestPasswordEncoder nos dá MD5 e SHA-512
-
Pbkdf2PasswordEncoder nos dá PBKDF2
-
BCryptPasswordEncoder nos dá BCrypt, e
-
SCryptPasswordEncoder nos dá SCrypt
Os codificadores de senha para PBKDF2, BCrypt e SCrypt vêm com suporte para configurar a força desejada do hash de senha.
Podemos usar esses codificadores diretamente, mesmo sem ter um aplicativo baseado no Spring Security. Ou, se estivermos protegendo nosso site com Spring Security, podemos configurar nosso codificador de senha desejado por meio de seu DSL ou por meio dedependency injection.
E, ao contrário de nossos exemplos acima,these encryption algorithms will generate the salt for us internally. O algoritmo armazena o sal no hash de saída para uso posterior na validação de uma senha.
7. Conclusão
Então, demos um mergulho profundo no hashing de senha explorando o conceito e seus usos.
E vimos algumas funções hash históricas, bem como algumas implementadas atualmente, antes de codificá-las em Java.
Finalmente, vimos que o Spring Security é fornecido com suas classes de criptografia de senha, implementando uma matriz de diferentes funções de hash.
Como sempre, o código éavailable on GitHub.