Comment puis-je hacher un mot de passe en Java?

j'ai besoin de hachage des mots de passe pour le stockage dans une base de données. Comment puis-je faire cela à Java?

j'espérais prendre le mot de passe en clair, ajouter un sel aléatoire, puis stocker le sel et le mot de passe dans la base de données.

puis quand un utilisateur a voulu se connecter, je pourrais prendre leur mot de passe soumis, ajouter le sel aléatoire à partir de leurs informations de Compte, hachez-le et voir si elle égale au mot de passe de hachage stocké avec leurs informations de Compte.

149
demandé sur durron597 2010-05-19 00:35:16

10 réponses

vous pouvez en fait utiliser une installation intégrée à L'exécution Java pour le faire. Le SunJCE en Java 6 supporte PBKDF2, qui est un bon algorithme à utiliser pour le hachage de mot de passe.

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));

Voici une classe utilitaire que vous pouvez utiliser pour L'authentification par mot de passe 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 = "$";

  /**
   * 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("\\$(\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);
  }

}
132
répondu erickson 2017-10-23 21:41:59

Voici une mise en œuvre complète avec deux méthodes pour faire exactement ce que vous voulez:

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

le fait est que même si un attaquant obtient l'accès à votre base de données et le code source, les mots de passe sont toujours sûrs.

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());
    }
}

nous stockons 'salt$iterated_hash(password, salt)' . Le sel sont 32 octets aléatoires et il est but est que si deux personnes différentes choisissent le même mot de passe, les mots de passe stockés seront toujours regarder différent.

le iterated_hash , qui est essentiellement hash(hash(hash(... hash(password, salt) ...))) rend très coûteux pour un attaquant potentiel qui a accès à votre base de données pour deviner les mots de passe, les hachage, et chercher des hachages dans la base de données. Vous devez calculer ce iterated_hash chaque fois qu'un utilisateur se connecte, mais cela ne vous coûte pas grand chose par rapport à l'attaquant qui passe près de 100% de son temps de calcul des hachages.

86
répondu Martin Konicek 2018-09-02 11:23:35

BCrypt est une très bonne bibliothèque, et il ya un Port Java de celui-ci.

27
répondu Michael Borgwardt 2010-05-18 20:37:36

vous pouvez calculer des Hash en utilisant MessageDigest , mais c'est mal en termes de sécurité. Les hachures ne doivent pas être utilisées pour stocker les mots de passe, car elles sont facilement cassables.

vous devez utiliser un autre algorithme comme bcrypt, PBKDF2 et scrypt pour stocker vos mots de passe. voir ici .

7
répondu Bozho 2015-10-13 15:11:04

vous pouvez utiliser le Shiro bibliothèque (anciennement JSecurity ) mise en œuvre de ce qui est décrit par OWASP .

il semble aussi que la bibliothèque JASYPT a un utilité similaire .

6
répondu laz 2010-05-18 21:01:00

en plus de bcrypt et PBKDF2 mentionnés dans d'autres réponses, je recommande de regarder scrypt

MD5 et SHA-1 ne sont pas recommandés car ils sont relativement rapides donc en utilisant le "loyer par heure" de l'informatique distribuée (par exemple EC2) ou un GPU haut de gamme moderne on peut "craquer" des mots de passe en utilisant la force brute / attaques de dictionnaires dans des coûts relativement faibles et un temps raisonnable.

si vous devez les utiliser, puis au moins itérer le algorithme nombre de fois significatif prédéfini (1000+).

6
répondu Eran Medan 2017-03-17 13:14:46

entièrement d'accord avec Erickson que PBKDF2 est la réponse.

si vous n'avez pas cette option, ou si vous n'avez besoin que d'utiliser un hash, Apache Commons DigestUtils est beaucoup plus facile que D'obtenir le bon code JCE: https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html

si vous utilisez un hash, allez avec sha256 ou sha512. Cette page a de bonnes recommandations sur manipulation du mot de passe et le hachage (notez qu'il ne recommande pas le hachage pour la manipulation du mot de passe)): http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

6
répondu David Carboni 2015-09-23 19:35:29

ici vous avez deux liens pour le hachage MD5 et d'autres méthodes de hachage:

API Javadoc: http://java.sun.com/j2se/1.4.2/docs/api/java/security/MessageDigest.html

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

3
répondu Simon 2010-05-18 20:38:27

alors que la recommandation NIST PBKDF2 a déjà été mentionnée, je tiens à souligner qu'il y avait un public concurrence de hachage de mot de passe qui a fonctionné de 2013 à 2015. En fin de compte, Argon2 a été choisi comme la fonction de hachage de mot de passe recommandée.

il ya un assez bien adopté Java binding pour l'original (natif c) bibliothèque que vous pouvez utiliser.

dans le cas d'utilisation moyenne, Je ne pense pas qu'il importe du point de vue de la sécurité si vous choisissez PBKDF2 plutôt que Argon2 ou vice-versa. Si vous avez des exigences de sécurité élevées, je vous recommande de prendre en compte Argon2 dans votre évaluation.

pour plus d'informations sur la sécurité des fonctions de hachage de mot de passe, voir sécurité.se .

3
répondu Qw3ry 2017-07-05 10:30:44

parmi tous les schémas de hachage standard, LDAP ssha est le plus sûr à utiliser,

http://www.openldap.org/faq/data/cache/347.html

je suivrais simplement les algorithmes spécifiés et utiliserais MessageDigest pour faire le hachage.

vous devez stocker le sel dans votre base de données comme vous l'avez suggéré.

1
répondu ZZ Coder 2010-05-18 20:43:11