¿Cómo puedo descifrar una contraseña en Java?

176

Necesito hash contraseñas para el almacenamiento en una base de datos. ¿Cómo puedo hacer esto en Java?

Esperaba tomar la contraseña de texto sin formato, agregar una sal aleatoria, luego almacenar la sal y la contraseña hash en la base de datos.

Luego, cuando un usuario deseaba iniciar sesión, podía tomar su contraseña enviada, agregar la sal aleatoria de la información de su cuenta, hacer un hash y ver si equivale a la contraseña de hash almacenada con la información de su cuenta.

Chris Dutrow
fuente
11
@YGL esto en realidad no es una recombinación hoy en día con ataques de GPU tan baratos, la familia SHA es en realidad una muy mala opción para el hash de contraseñas (demasiado rápido) incluso con sal. Use bcrypt, scrypt o PBKDF2
Eran Medan
11
¿Por qué se cerró esta pregunta? Esta es una pregunta para un problema de ingeniería real, y las respuestas son invaluables. El OP no está pidiendo una biblioteca, está preguntando cómo resolver el problema de ingeniería.
stackoverflowuser2010
12
Simplemente asombroso. Esta pregunta tiene 52 votos a favor, y alguien decide cerrarla como "fuera de tema".
stackoverflowuser2010
1
Sí, he publicado en Meta sobre este tema de cierres antes, sin embargo, me golpearon bastante mal.
Chris Dutrow
8
Esta pregunta debe reabrirse. Se trata de cómo escribir un programa para resolver el problema descrito (autenticación de contraseña), con una solución de código corto. Ver la palabra desencadenante "biblioteca" no justifica cerrar reflexivamente una pregunta; no está pidiendo una recomendación de biblioteca, está preguntando cómo hacer hash de contraseñas. Editar: Allí, lo arregló.
erickson

Respuestas:

157

En realidad, puede usar 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 hash 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://stackoverflow.com/a/2861125/3474">StackOverflow</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);
  }

}
erickson
fuente
11
Es posible que tenga cuidado con las conversiones de byte a hexadecimal con BigInteger: se eliminan los ceros a la izquierda. Eso está bien para la depuración rápida, pero he visto errores en el código de producción debido a ese efecto.
Thomas Pornin
24
@ thomas-pornin destaca por qué necesitamos una biblioteca , no un bloque de código que ya casi está allí. Da miedo que la respuesta aceptada no responda la pregunta sobre un tema tan importante.
Nilzor
9
Use el algoritmo PBKDF2WithHmacSHA512 comenzando con Java 8. Es un poco más fuerte.
iwan.z
1
Nota, algs existentes no se eliminan en las versiones posteriores: java_4: PBEWithMD5AndDES, Desede, DES java_5 / 6/7: PBKDF2WithHmacSHA1, PBE (sólo en Java 5), PBEWithSHA1AndRC2_40, PBEWithSHA1And, PBEWithMD5AndTriple java_8: PBEWithHmacSHA224AndAES_128, PBEWithHmacSHA384AndAES_128, PBEWithHmacSHA512AndAES_128, RC4_40, PBKDF2WithHmacSHA256 , PBEWithHmacSHA1AndAES_128, RC4_128, PBKDF2WithHmacSHA224, PBEWithHmacSHA256AndAES_256, RC2_128, PBEWithHmacSHA224AndAES_256, PBEWithHmacSHA384AndAES_256, PBEWithHmacSHA512AndAES_256, PBKDF2WithHmacSHA512, PBEWithHmacSHA256AndAES_128, PBKDF2WithHmacSHA384, PBEWithHmacSHA1AndAES_256
iwan.z
44
@TheTosters Sí, el tiempo de ejecución será más largo para contraseñas incorrectas ; más específicamente, las contraseñas incorrectas tomarán el mismo tiempo que las contraseñas correctas. Evita los ataques de tiempo, aunque confieso que no puedo pensar en una forma práctica de explotar tal vulnerabilidad en este caso. Pero no cortas esquinas. El hecho de que no pueda verlo no significa que una mente más desviada no lo haga.
erickson
97

Aquí hay una implementación completa con dos métodos que hacen exactamente lo que quieres:

String getSaltedHash(String password)
boolean checkPassword(String password, String stored)

El punto es que incluso si un atacante obtiene 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[] saltAndHash = stored.split("\\$");
        if (saltAndHash.length != 2) {
            throw new IllegalStateException(
                "The stored password must have the form 'salt$hash'");
        }
        String hashOfInput = hash(password, Base64.decodeBase64(saltAndHash[0]));
        return hashOfInput.equals(saltAndHash[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 son 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 es básicamente hash(hash(hash(... hash(password, salt) ...)))lo hace muy caro para un atacante potencial que tiene acceso a la base de datos de contraseñas de adivinar, hash de ellos, y buscar los hashes en la base de datos. Debe calcular esto iterated_hashcada vez que un usuario inicia sesión, pero no le cuesta tanto en comparación con el atacante que gasta casi el 100% de su tiempo calculando hashes.

Martin Konicek
fuente
14
Lamento molestar, pero ¿por qué debería elegir esto sobre una biblioteca existente? Una biblioteca probablemente tiene una mayor probabilidad de ser revisada a fondo. Dudo que cada uno de los 14 votos positivos analizó el código en busca de algún problema.
Joachim Sauer
2
@JoachimSauer Esto es esencialmente solo usando una biblioteca (javax.crypto) pero tienes razón: no se admiten contraseñas vacías. Se agregó una excepción para hacerlo explícito. ¡Gracias!
Martin Konicek
3
Probablemente debería cambiar las firmas de métodos a en char[] passwordlugar de String password.
Assylias
2
Aunque parece que la referencia no recibe un acuerdo unánime. Vea también esto: security.stackexchange.com/a/20369/12614
assylias el
3
¿Está seguro de que .equals () en las cadenas no produce un cortocircuito (es decir, deja de hacer bucles cuando encuentra dos bytes que no son iguales)? Si lo hace, existe el riesgo de que un ataque de sincronización filtre información sobre el hash de contraseña.
bobpoekert
28

BCrypt es una muy buena biblioteca, y tiene un puerto Java .

Michael Borgwardt
fuente
7

Puede usar la implementación de la biblioteca Shiro (anteriormente JSecurity ) de lo que describe OWASP .

También parece que la biblioteca JASYPT tiene una utilidad similar .

perezoso
fuente
Eso es realmente lo que estaba usando. Pero como decidimos no usar Shiro, había cierta preocupación por la ineficiencia de tener que incluir toda la biblioteca de Shiro para ese único paquete.
Chris Dutrow
No conozco una biblioteca compuesta solo por una utilidad de hash de contraseña. Probablemente sea mejor rodar la suya si las dependencias son una preocupación. La respuesta de erickson me parece bastante buena. O simplemente copie el código de ese enlace OWASP al que hice referencia si prefiere usar SHA de manera segura.
laz
7

Puede compilar hash utilizando 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 rompibles.

Debe usar otro algoritmo como bcrypt, PBKDF2 y scrypt para almacenar sus contraseñas. Vea aquí .

Bozho
fuente
3
¿Cómo hash hash la contraseña al iniciar sesión sin almacenar sal en la base de datos?
ZZ Coder
9
Usar el nombre de usuario como sal no es un defecto fatal, pero no es tan bueno como usar una sal de un RNG criptográfico. Y no hay absolutamente ningún problema para almacenar la sal en la base de datos. La sal no es secreta.
erickson
1
¿No se almacenarían también el nombre de usuario y el correo electrónico en la base de datos?
Chris Dutrow
@ZZ Coder, @erickson correcto, de alguna manera asumí que sería una sal para todas las contraseñas, lo que conduciría a una tabla de arco iris fácilmente computable.
Bozho
13
Un problema con el uso del nombre de usuario (u otra ID como el correo electrónico) es que no puedes cambiar la ID sin que el usuario también establezca una nueva contraseña.
Lawrence Dol
6

Además de bcrypt y PBKDF2 mencionados en otras respuestas, recomendaría mirar scrypt

MD5 y SHA-1 no se recomiendan, ya que son relativamente rápidos, por lo tanto, el uso de computación distribuida de "alquiler por hora" (por ejemplo, EC2) o una GPU moderna de alta gama puede "descifrar" las contraseñas usando ataques de fuerza bruta / diccionario en costos relativamente bajos y razonables hora.

Si debe usarlos, al menos repita el algoritmo una cantidad significativa predefinida de veces (1000+).

Eran Medan
fuente
6

Totalmente 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 y el hash de contraseñas (tenga en cuenta que no recomienda el hash para el manejo de contraseñas): http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

David Carboni
fuente
Vale la pena señalar que SHA512 no es mejor que SHA256 (para este propósito) solo porque el número es mayor.
Azsgy
5

Se podría utilizar Spring Security Crypto (tiene sólo 2 dependencias de compilación opcionales ), que apoya PBKDF2 , Bcrypt , Scrypt y ARGON2 cifrado de la contraseña.

Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder();
String aCryptedPassword = argon2PasswordEncoder.encode("password");
boolean passwordIsValid = argon2PasswordEncoder.matches("password", aCryptedPassword);
SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder();
String sCryptedPassword = sCryptPasswordEncoder.encode("password");
boolean passwordIsValid = sCryptPasswordEncoder.matches("password", sCryptedPassword);
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String bCryptedPassword = bCryptPasswordEncoder.encode("password");
boolean passwordIsValid = bCryptPasswordEncoder.matches("password", bCryptedPassword);
Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();
String pbkdf2CryptedPassword = pbkdf2PasswordEncoder.encode("password");
boolean passwordIsValid = pbkdf2PasswordEncoder.matches("password", pbkdf2CryptedPassword);
BuZZ-dEE
fuente
4

Si bien la recomendación NIST PBKDF2 ya se ha mencionado, me gustaría señalar que hubo una competencia de hash de contraseña pública que se desarrolló entre 2013 y 2015. Al final, se eligió Argon2 como la función de hash de contraseña recomendada.

Existe un enlace de Java bastante bien adoptado para la biblioteca original (C nativa) 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 sólidos, le recomiendo considerar Argon2 en su evaluación.

Para obtener más información sobre la seguridad de las funciones de hash de contraseña, consulte security.se .

Qw3ry
fuente
@zaph Edité la respuesta para ser más objetivo. Tenga en cuenta que la recomendación NIST puede no ser siempre la mejor opción (consulte aquí para ver un ejemplo); por supuesto, esto también es válido para cualquier cosa que se recomiende en otro lugar. Por lo tanto, creo que esta respuesta proporciona un valor a esta pregunta.
Qw3ry
2

Aquí tiene dos enlaces para el hash MD5 y otros métodos de hash:

API Javadoc: https://docs.oracle.com/javase/1.5.0/docs/api/java/security/MessageDigest.html

Tutorial: http://www.twmacinta.com/myjava/fast_md5.php

Simón
fuente
3
Solo tenga en cuenta que para el hashing de contraseñas, más lento es mejor. Debe usar miles de iteraciones de la función hash como técnica de "fortalecimiento de claves". Además, la sal es imprescindible.
erickson
Tenía la impresión de que múltiples iteraciones de un algoritmo de hash de calidad producirían aproximadamente la misma seguridad que una iteración, ya que la longitud de los bytes seguiría siendo la misma.
Chris Dutrow
@erickson Sería mejor reducir la velocidad de los atacantes explícitamente.
Deamon
66
Acerca del fortalecimiento clave: las sales existen para hacer inutilizables los hash precalculados. Pero los atacantes no tienen que calcular previamente. Los atacantes pueden simplemente cortar cadenas + sal "sobre la marcha" hasta que encuentren la correcta. Pero si iteras miles de veces para tus hashes, tendrán que hacer lo mismo. Su servidor no se verá muy afectado por las iteraciones de 10k, ya que no sucede con tanta frecuencia. Los atacantes necesitarán 10k veces la potencia informática.
zockman
2
@Simon hoy MD5 se considera inútil para el hash de contraseñas, ya que se puede descifrar en segundos usando los ataques de fuerza bruta / diccionario de la GPU. Ver aquí: codahale.com/how-to-safely-store-a-password
Eran Medan
1

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ía MessageDigest para hacer el hash.

Necesita almacenar la sal en su base de datos como sugirió.

Codificador ZZ
fuente
1
Como SSHA no repite la función hash, es demasiado rápido. Esto permite a los atacantes probar contraseñas más rápidamente. Los mejores algoritmos como Bcrypt, PBBKDF1 y PBKDF2 utilizan técnicas de "fortalecimiento de claves" para retrasar a los atacantes hasta el punto en que una contraseña debe caducar antes de que puedan forzar la fuerza bruta, incluso un espacio de contraseña de 8 letras.
erickson
El problema con todos estos mecanismos es que no obtienes soporte al cliente. El problema con la contraseña hash es que no puede admitir hash de contraseña con otros algoritmos. Con ssha, al menos todos los clientes LDAP lo admiten.
ZZ Coder
No es "más seguro", es simplemente "bastante compatible". bcrypt / scrypt son mucho más intensivos en recursos.
Eckes
1

A partir de 2020, el algoritmo más creíble y flexible en uso,

el más probable para optimizar su fuerza dado cualquier hardware,

es Argon2id o Argon2i .

Proporciona la herramienta de calibración necesaria para encontrar parámetros de resistencia optimizados dado un tiempo de hashing objetivo y el hardware utilizado.

  • Argon2i está especializado en hashing codicioso de memoria
  • Argon2d está especializado en hashing codicioso de CPU
  • Argon2id usa ambos métodos.

El hash codicioso de la memoria ayudaría contra el uso de GPU para el craqueo.

La implementación de Spring Security / Bouncy Castle no está optimizada y relativamente semana dado lo que el atacante podría usar. cf: documentación de primavera

La implementación actual utiliza el castillo Bouncy, que no explota el paralelismo / optimizaciones que usarán los crackers de contraseñas, por lo que existe una asimetría innecesaria entre el atacante y el defensor.

La implementación más creíble en uso para Java es la de mkammerer ,

Un contenedor contenedor / biblioteca de la implementación nativa oficial escrita en Rust.

Está bien escrito y es fácil de usar.

La versión integrada proporciona compilaciones nativas para Linux, Windows y OSX.

Como ejemplo, es utilizado por jpmorganchase en su proyecto de seguridad tessera utilizado para asegurar Quorum , su implementación de criptomonedas Ethereum.

Aquí está el código de ejemplo de tessera.

La calibración se puede realizar usando de.mkammerer.argon2.Argon2Helper # findIterations

Los algoritmos SCRYPT y Pbkdf2 también pueden calibrarse escribiendo algunos puntos de referencia simples, pero los valores mínimos de iteraciones seguras actuales requerirán tiempos de hashing más altos.

usuario1767316
fuente
0

Lo aprendí de un video en Udemy y lo edité para que sea una contraseña aleatoria más segura

}

private String pass() {
        String passswet="1234567890zxcvbbnmasdfghjklop[iuytrtewq@#$%^&*" ;

        char icon1;
        char[] t=new char[20];

         int rand1=(int)(Math.random()*6)+38;//to make a random within the range of special characters

            icon1=passswet.charAt(rand1);//will produce char with a special character

        int i=0;
        while( i <11) {

             int rand=(int)(Math.random()*passswet.length());
             //notice (int) as the original value of Math>random() is double

             t[i] =passswet.charAt(rand);

             i++;
                t[10]=icon1;
//to replace the specified item with icon1
         }
        return new String(t);
}






}
سحنون المالكى
fuente
Estoy dispuesto a ser corregido, pero creo que no deberías usar números aleatorios al hacer hash. Esto es para que su función hash siga siendo determinista; es decir, si hash una cadena varias veces, siempre obtendrá el mismo valor hash para esa cadena.
duldi