Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:
>> VER EL CURSO1. Información general
En este tutorial, discutiremos la importancia del hash de contraseñas.
Echaremos un vistazo rápido a qué es, por qué es importante y algunas formas seguras e inseguras de hacerlo en Java.
2. ¿Qué es Hash?
El hash es el proceso de generar una cadena, o hash , a partir de un mensaje dado utilizando una función matemática conocida como función hash criptográfica .
Si bien existen varias funciones hash, las que se adaptan al hash de contraseñas deben tener cuatro propiedades principales para ser seguras:
- Debe ser determinista : el mismo mensaje procesado por la misma función hash siempre debe producir el mismo hash
- No es reversible : no es práctico generar un mensaje a partir de su hash
- Tiene una alta entropía : un pequeño cambio en un mensaje debería producir un hash muy diferente
- Y resiste colisiones : dos mensajes diferentes no deberían producir el mismo hash
Una función hash que tiene las cuatro propiedades es un buen candidato para el hash de contraseñas, ya que juntas aumentan drásticamente la dificultad de realizar ingeniería inversa de la contraseña a partir del hash.
Además, las funciones de hash de contraseñas deberían ser lentas . Un algoritmo rápido ayudaría a los ataques de fuerza bruta en los que un pirata informático intentará adivinar una contraseña haciendo hash y comparando miles de millones (o billones) de contraseñas potenciales por segundo.
Algunas funciones hash excelentes que cumplen con todos estos criterios sonPBKDF2, Bcrypt, y Scrypt. Pero primero, echemos un vistazo a algunos algoritmos más antiguos y por qué ya no se recomiendan
3. No recomendado: MD5
Nuestra primera función hash es el algoritmo de resumen de mensajes MD5, desarrollado en 1992.
MessageDigest de Java hace que esto sea fácil de calcular y aún puede ser útil en otras circunstancias.
Sin embargo, durante los últimos años, se descubrió que MD5 fallaba en la cuarta propiedad de hash de contraseña, ya que se volvió computacionalmente fácil generar colisiones. Para colmo, MD5 es un algoritmo rápido y, por lo tanto, inútil contra ataques de fuerza bruta.
Debido a esto, no se recomienda MD5.
4. No recomendado: SHA-512
A continuación, veremos SHA-512, que es parte de la familia Secure Hash Algorithm, una familia que comenzó con SHA-0 en 1993.
4.1. ¿Por qué SHA-512?
A medida que las computadoras aumentan de potencia y encontramos nuevas vulnerabilidades, los investigadores obtienen nuevas versiones de SHA. Las versiones más nuevas tienen una longitud progresivamente más larga o, a veces, los investigadores publican una nueva versión del algoritmo subyacente.
SHA-512 representa la clave más larga de la tercera generación del algoritmo.
Si bien ahora hay versiones más seguras de SHA , SHA-512 es la más sólida que se implementa en Java.
4.2. Implementando en Java
Ahora, echemos un vistazo a la implementación del algoritmo hash SHA-512 en Java.
Primero, tenemos que entender el concepto de sal . En pocas palabras, esta es una secuencia aleatoria que se genera para cada nuevo hash .
Al introducir esta aleatoriedad, aumentamos la entropía del hash y protegemos nuestra base de datos contra listas precompiladas de hashes conocidas como tablas arcoíris .
Nuestra nueva función hash se convierte entonces aproximadamente en:
salt <- generate-salt; hash <- salt + ':' + sha512(salt + password)
4.3. Generando una sal
Para introducir la sal, usaremos el SecureRandom clase de java.security :
SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt);
Luego, usaremos la clase MessageDigest para configurar la función hash SHA-512 con nuestro salt:
MessageDigest md = MessageDigest.getInstance("SHA-512"); md.update(salt);
Y con eso agregado, ahora podemos usar el método de resumen para generar nuestra contraseña hash:
byte[] hashedPassword = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
4.4. ¿Por qué no se recomienda?
Cuando se emplea con sal, SHA-512 sigue siendo una opción justa, pero existen opciones más fuertes y más lentas .
Además, las opciones restantes que cubriremos tienen una característica importante: fuerza configurable.
5. PBKDF2, BCrypt y SCrypt
PBKDF2, BCrypt y SCrypt son tres algoritmos recomendados.
5.1. ¿Por qué se recomiendan?
Cada uno de estos es lento y cada uno tiene la brillante característica de tener una fuerza configurable.
This means that as computers increase in strength, we can slow down the algorithm by changing the inputs.
5.2. Implementing PBKDF2 in Java
Now, salts are a fundamental principle of password hashing, and so we need one for PBKDF2, too:
SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt);
Next, we'll create a PBEKeySpec and a SecretKeyFactory which we'll instantiate using the PBKDF2WithHmacSHA1 algorithm:
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
The third parameter (65536) is effectively the strength parameter. It indicates how many iterations that this algorithm run for, increasing the time it takes to produce the hash.
Finally, we can use our SecretKeyFactory to generate the hash:
byte[] hash = factory.generateSecret(spec).getEncoded();
5.3. Implementing BCrypt and SCrypt in Java
So, it turns out that BCrypt and SCrypt support don't yet ship with Java, though some Java libraries support them.
One of those libraries is Spring Security.
6. Password Hashing With Spring Security
Although Java natively supports both the PBKDF2 and SHA hashing algorithms, it doesn't support BCrypt and SCrypt algorithms.
Luckily for us, Spring Security ships with support for all these recommended algorithms via the PasswordEncoder interface:
- MessageDigestPasswordEncoder gives us MD5 and SHA-512
- Pbkdf2PasswordEncoder gives us PBKDF2
- BCryptPasswordEncoder gives us BCrypt, and
- SCryptPasswordEncoder gives us SCrypt
The password encoders for PBKDF2, BCrypt, and SCrypt all come with support for configuring the desired strength of the password hash.
We can use these encoders directly, even without having a Spring Security-based application. Or, if we are protecting our site with Spring Security, then we can configure our desired password encoder through its DSL or via dependency injection.
And, unlike our examples above, these encryption algorithms will generate the salt for us internally. The algorithm stores the salt within the output hash for later use in validating a password.
7. Conclusion
So, we've taken a deep dive into password hashing; exploring the concept and its uses.
And we've taken a look at some historical hash functions as well as some currently implemented ones before coding them in Java.
Finalmente, vimos que Spring Security se envía con sus clases de cifrado de contraseñas, implementando una variedad de diferentes funciones hash.
Como siempre, el código está disponible en GitHub.
Fondo de Java