online - ¿Cómo puedo hash una contraseña en Java?
java encrypt password (10)
Además de bcrypt y PBKDF2 mencionados en otras respuestas, recomendaría mirar scrypt
MD5 y SHA-1 no son recomendables ya que son relativamente rápidos; por lo tanto, usan computación distribuida "alquiler por hora" (por ej. EC2) o una GPU de gama alta moderna puede "descifrar" contraseñas usando ataques de fuerza bruta / diccionario a costos relativamente bajos y razonables hora.
Si debe usarlos, al menos itere el algoritmo una cantidad de veces predefinida (1000+).
Consulte aquí para obtener más información: https://security.stackexchange.com/questions/211/how-to-securely-hash-passwords
Y aquí: http://codahale.com/how-to-safely-store-a-password/ (critica a la familia SHA, MD5, etc. por el uso de contraseñas)
- Y aquí: http://www.unlimitednovelty.com/2012/03/dont-use-bcrypt.html (critica bcrypt y recomienda scrypt y PBKDF2)
Necesito hash contraseñas para el almacenamiento en una base de datos. ¿Cómo puedo hacer esto en Java?
Tenía la esperanza de tomar la contraseña de texto sin formato, agregar una sal al azar, luego almacenar la sal y la contraseña hash en la base de datos.
Luego, cuando un usuario quería iniciar sesión, podía tomar su contraseña enviada, agregar la sal aleatoria de la información de su cuenta, hash it y ver si equivale a la contraseña hash almacenada con la información de su cuenta.
Aquí hay una implementación completa con dos métodos que hacen exactamente lo que desea:
String getSaltedHash(String password)
boolean checkPassword(String password, String stored)
El punto es que incluso si un atacante tiene acceso tanto a su base de datos como a su código fuente, las contraseñas siguen siendo seguras.
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Base64;
public class Password {
// The higher the number of iterations the more
// expensive computing the hash is for us and
// also for an attacker.
private static final int iterations = 20*1000;
private static final int saltLen = 32;
private static final int desiredKeyLen = 256;
/** Computes a salted PBKDF2 hash of given plaintext password
suitable for storing in a database.
Empty passwords are not supported. */
public static String getSaltedHash(String password) throws Exception {
byte[] salt = SecureRandom.getInstance("SHA1PRNG").generateSeed(saltLen);
// store the salt with the password
return Base64.encodeBase64String(salt) + "$" + hash(password, salt);
}
/** Checks whether given plaintext password corresponds
to a stored salted hash of the password. */
public static boolean check(String password, String stored) throws Exception{
String[] saltAndPass = stored.split("//$");
if (saltAndPass.length != 2) {
throw new IllegalStateException(
"The stored password have the form ''salt$hash''");
}
String hashOfInput = hash(password, Base64.decodeBase64(saltAndPass[0]));
return hashOfInput.equals(saltAndPass[1]);
}
// using PBKDF2 from Sun, an alternative is https://github.com/wg/scrypt
// cf. http://www.unlimitednovelty.com/2012/03/dont-use-bcrypt.html
private static String hash(String password, byte[] salt) throws Exception {
if (password == null || password.length() == 0)
throw new IllegalArgumentException("Empty passwords are not supported.");
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey key = f.generateSecret(new PBEKeySpec(
password.toCharArray(), salt, iterations, desiredKeyLen)
);
return Base64.encodeBase64String(key.getEncoded());
}
}
Estamos almacenando ''salt$iterated_hash(password, salt)''
. La sal es de 32 bytes aleatorios y su propósito es que si dos personas diferentes eligen la misma contraseña, las contraseñas almacenadas seguirán siendo diferentes.
El iterated_hash
, que básicamente es hash(hash(hash(... hash(password, salt) ...)))
hace que sea muy costoso para un atacante potencial que tiene acceso a su base de datos adivinar contraseñas, hash y buscar hash up en la base de datos. Tiene que calcular este iterated_hash
cada vez que un usuario inicia sesión, pero no le cuesta mucho en comparación con el atacante que gasta casi el 100% de sus hashes de computación de tiempo.
Aquí tiene dos enlaces para hashing MD5 y otros métodos hash:
API Javadoc: http://java.sun.com/j2se/1.4.2/docs/api/java/security/MessageDigest.html
En realidad, puede utilizar una instalación integrada en el tiempo de ejecución de Java para hacer esto. SunJCE en Java 6 es compatible con PBKDF2, que es un buen algoritmo para usar para el hashing de contraseñas.
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec("password".toCharArray(), salt, 65536, 128);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
System.out.printf("salt: %s%n", enc.encodeToString(salt));
System.out.printf("hash: %s%n", enc.encodeToString(hash));
Aquí hay una clase de utilidad que puede usar para la autenticación de contraseña PBKDF2:
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
* Hash passwords for storage, and test passwords against password tokens.
*
* Instances of this class can be used concurrently by multiple threads.
*
* @author erickson
* @see <a href="http://.com/a/2861125/3474"></a>
*/
public final class PasswordAuthentication
{
/**
* Each token produced by this class uses this identifier as a prefix.
*/
public static final String ID = "$31$";
/**
* The minimum recommended cost, used by default
*/
public static final int DEFAULT_COST = 16;
private static final String ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int SIZE = 128;
private static final Pattern layout = Pattern.compile("//$31//$(//d//d?)//$(.{43})");
private final SecureRandom random;
private final int cost;
public PasswordAuthentication()
{
this(DEFAULT_COST);
}
/**
* Create a password manager with a specified cost
*
* @param cost the exponential computational cost of hashing a password, 0 to 30
*/
public PasswordAuthentication(int cost)
{
iterations(cost); /* Validate cost */
this.cost = cost;
this.random = new SecureRandom();
}
private static int iterations(int cost)
{
if ((cost < 0) || (cost > 30))
throw new IllegalArgumentException("cost: " + cost);
return 1 << cost;
}
/**
* Hash a password for storage.
*
* @return a secure authentication token to be stored for later authentication
*/
public String hash(char[] password)
{
byte[] salt = new byte[SIZE / 8];
random.nextBytes(salt);
byte[] dk = pbkdf2(password, salt, 1 << cost);
byte[] hash = new byte[salt.length + dk.length];
System.arraycopy(salt, 0, hash, 0, salt.length);
System.arraycopy(dk, 0, hash, salt.length, dk.length);
Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
return ID + cost + ''$'' + enc.encodeToString(hash);
}
/**
* Authenticate with a password and a stored password token.
*
* @return true if the password and token match
*/
public boolean authenticate(char[] password, String token)
{
Matcher m = layout.matcher(token);
if (!m.matches())
throw new IllegalArgumentException("Invalid token format");
int iterations = iterations(Integer.parseInt(m.group(1)));
byte[] hash = Base64.getUrlDecoder().decode(m.group(2));
byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8);
byte[] check = pbkdf2(password, salt, iterations);
int zero = 0;
for (int idx = 0; idx < check.length; ++idx)
zero |= hash[salt.length + idx] ^ check[idx];
return zero == 0;
}
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations)
{
KeySpec spec = new PBEKeySpec(password, salt, iterations, SIZE);
try {
SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM);
return f.generateSecret(spec).getEncoded();
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Missing algorithm: " + ALGORITHM, ex);
}
catch (InvalidKeySpecException ex) {
throw new IllegalStateException("Invalid SecretKeyFactory", ex);
}
}
/**
* Hash a password in an immutable {@code String}.
*
* <p>Passwords should be stored in a {@code char[]} so that it can be filled
* with zeros after use instead of lingering on the heap and elsewhere.
*
* @deprecated Use {@link #hash(char[])} instead
*/
@Deprecated
public String hash(String password)
{
return hash(password.toCharArray());
}
/**
* Authenticate with a password in an immutable {@code String} and a stored
* password token.
*
* @deprecated Use {@link #authenticate(char[],String)} instead.
* @see #hash(String)
*/
@Deprecated
public boolean authenticate(String password, String token)
{
return authenticate(password.toCharArray(), token);
}
}
Entre todos los esquemas hash estándar, LDAP ssha es el más seguro de usar,
http://www.openldap.org/faq/data/cache/347.html
Simplemente seguiría los algoritmos especificados allí y usar MessageDigest para hacer el hash.
Necesita almacenar la sal en su base de datos como sugirió.
Estoy completamente de acuerdo con Erickson en que PBKDF2 es la respuesta.
Si no tiene esa opción, o solo necesita usar un hash, Apache Commons DigestUtils es mucho más fácil que obtener el código JCE correcto: https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html
Si usa un hash, vaya con sha256 o sha512. Esta página tiene buenas recomendaciones sobre el manejo de contraseñas y hash (tenga en cuenta que no recomienda hashing para el manejo de contraseñas): http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
Puede compilar hashes usando MessageDigest
, pero esto es incorrecto en términos de seguridad. Los hashes no se deben usar para almacenar contraseñas, ya que son fácilmente frágiles.
Debería usar otro algoritmo como bcrypt, PBKDF2 y scrypt para almacenar sus contraseñas. Mira aquí .
Puede usar la implementation de la biblioteca Shiro (anteriormente JSecurity ) de lo que describe OWASP .
También parece que la biblioteca JASYPT tiene una utilidad similar .
Si bien ya se mencionó la recomendación NIST PBKDF2 , me gustaría señalar que hubo una competencia pública de hash de contraseñas que se desarrolló entre 2013 y 2015. Al final, se eligió a Argon2 como la función de hash de contraseñas recomendada.
Hay un enlace Java bastante bien adoptado para la biblioteca original (nativa C) que puede usar.
En el caso de uso promedio, no creo que importe desde una perspectiva de seguridad si elige PBKDF2 sobre Argon2 o viceversa. Si tiene requisitos de seguridad estrictos, le recomiendo considerar Argon2 en su evaluación.
Para obtener más información sobre la seguridad de las funciones de hash de contraseñas, consulte security.se .