Jak mogę hashować hasło w Javie?

Muszę hashować hasła do przechowywania w bazie danych. Jak mogę to zrobić w Javie?

Miałem nadzieję wziąć hasło tekstowe, dodać losową sól, a następnie przechowywać sól i hashowane hasło w bazie danych.

Wtedy, gdy użytkownik chciał się zalogować, mogłem wziąć swoje podane hasło, dodać losową sól z jego informacji o koncie, hash go i sprawdzić, czy jest to równoznaczne z zapisanym hash hasło z jego informacji o koncie.

Author: durron597, 2010-05-19

10 answers

Można w tym celu użyć obiektu wbudowanego w środowisko Java runtime. SunJCE w Javie 6 obsługuje PBKDF2, który jest dobrym algorytmem do hashowania haseł.

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

Oto Klasa użytkowa, której możesz użyć do uwierzytelniania hasłem 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);
  }

}
 132
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-23 21:41:59

Oto pełna implementacja z dwiema metodami robiącymi dokładnie to, co chcesz:

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

Chodzi o to, że nawet jeśli atakujący uzyska dostęp zarówno do bazy danych, jak i kodu źródłowego, hasła są nadal bezpieczne.

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

Przechowujemy 'salt$iterated_hash(password, salt)'. Salt to 32 losowe bajty, a jego celem jest to, że jeśli dwie różne osoby wybierają to samo hasło, zapisane hasła będą nadal wyglądać inaczej.

iterated_hash, który jest w zasadzie hash(hash(hash(... hash(password, salt) ...))) sprawia, że jest bardzo drogi potencjalny atakujący, który ma dostęp do twojej bazy danych, może odgadnąć hasła, hashować je i wyszukać hasze w bazie danych. Musisz to obliczyć iterated_hash za każdym razem, gdy użytkownik się loguje, ale nie kosztuje to tak dużo w porównaniu z atakującym, który spędza prawie 100% swojego czasu na obliczanie skrótów.

 86
Author: Martin Konicek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-02 11:23:35

BCrypt jest bardzo dobrą biblioteką i znajduje się w niej port Java.

 27
Author: Michael Borgwardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-18 20:37:36

Możesz obliczyć hashe używając MessageDigest, ale to jest złe pod względem bezpieczeństwa. Hashów nie należy używać do przechowywania haseł, ponieważ łatwo je łamać.

Powinieneś użyć innego algorytmu, takiego jak bcrypt, PBKDF2 i scrypt do przechowywania haseł. Zobacz tutaj .

 7
Author: Bozho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-13 15:11:04

Możesz użyć Shiro biblioteki (dawniej JSecurity) implementacja tego, co jest opisane przez OWASP .

Wygląda na to, że biblioteka JASYPT ma podobne narzędzie.

 6
Author: laz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-18 21:01:00

Oprócz bcrypt i PBKDF2 wymienionych w innych odpowiedziach, polecam spojrzeć na scrypt

MD5 i SHA-1 nie są zalecane, ponieważ są stosunkowo szybkie, więc używając rozproszonych obliczeń "rent per hour" (np. EC2) lub nowoczesnego wysokiej klasy GPU można" złamać " hasła za pomocą ataków brute force / słownikowych w stosunkowo niskich kosztach i rozsądnym czasie.

Jeśli musisz z nich korzystać, to przynajmniej powtarzaj algorytm z predefiniowanej znaczącej ilości razy (1000+).

 6
Author: Eran Medan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 13:14:46

W pełni zgadzam się z Ericksonem, że PBKDF2 jest odpowiedzią.

Jeśli nie masz tej opcji lub potrzebujesz tylko użyć hasha, Apache Commons DigestUtils jest znacznie łatwiejsze niż poprawienie kodu JCE: https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html

Jeśli używasz hasha, wybierz sha256 lub sha512. Ta strona zawiera dobre zalecenia dotyczące obsługi i haszowania haseł (pamiętaj, że nie zaleca się haszowania dla hasła obsługa): http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

 6
Author: David Carboni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-23 19:35:29

Tutaj masz dwa linki do hashowania MD5 i innych metod hashowania:

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

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

 3
Author: Simon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-18 20:38:27

Chociaż rekomendacja NIST PBKDF2 została już wspomniana, chciałbym zwrócić uwagę, że w latach 2013-2015 odbył się publiczny konkurs mieszania haseł . In the end, Argon2 została wybrana jako zalecana funkcja haszowania haseł.

Istnieje dość dobrze przyjęte Wiązanie Javy dla oryginalnej (natywnej) biblioteki C, której możesz użyć.

W przeciętnym przypadku użycia, nie sądzę, że ma to znaczenie z bezpieczeństwa perspektywa, jeśli wybierzesz PBKDF2 zamiast Argon2 lub odwrotnie. Jeśli masz silne wymagania bezpieczeństwa, zalecam rozważenie Argon2 w swojej ocenie.

Aby uzyskać więcej informacji na temat bezpieczeństwa funkcji haszujących hasła, zobacz security.se .

 3
Author: Qw3ry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 10:30:44

Wśród wszystkich standardowych schematów hashowych, LDAP ssha jest najbezpieczniejszym do użycia,

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

Po prostu postępowałbym zgodnie z podanymi tam algorytmami i użyłbym MessageDigest do wykonania hasha.

Musisz przechowywać sól w bazie danych zgodnie z sugestią.

 1
Author: ZZ Coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-18 20:43:11