如何在 Java 中散列密码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2860943/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 13:48:20  来源:igfitidea点击:

How can I hash a password in Java?

javapasswordscryptographic-hash-function

提问by Chris Dutrow

I need to hash passwords for storage in a database. How can I do this in Java?

我需要散列密码以存储在数据库中。我怎样才能在 Java 中做到这一点?

I was hoping to take the plain text password, add a random salt, then store the salt and the hashed password in the database.

我希望采用纯文本密码,添加随机盐,然后将盐和散列密码存储在数据库中。

Then when a user wanted to log in, I could take their submitted password, add the random salt from their account information, hash it and see if it equates to the stored hash password with their account information.

然后当用户想要登录时,我可以获取他们提交的密码,从他们的帐户信息中添加随机盐,对其进行哈希处理,看看它是否等于存储的哈希密码和他们的帐户信息。

采纳答案by erickson

You can actually use a facility built in to the Java runtime to do this. The SunJCE in Java 6 supports PBKDF2, which is a good algorithm to use for password hashing.

您实际上可以使用内置于 Java 运行时的工具来执行此操作。Java 6 中的 SunJCE 支持 PBKDF2,这是一种用于密码散列的好算法。

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

Here's a utility class that you can use for PBKDF2 password authentication:

这是一个可用于 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);
  }

}

回答by Michael Borgwardt

BCrypt is a very good library, and there is a Java portof it.

BCrypt 是一个非常好的库,并且有它的Java 端口

回答by Bozho

You can comput hashes using MessageDigest, but this is wrong in terms of security. Hashes are not to be used for storing passwords, as they are easily breakable.

您可以使用 计算哈希值MessageDigest,但从安全性方面来看这是错误的。哈希不能用于存储密码,因为它们很容易被破解。

You should use another algorithm like bcrypt, PBKDF2 and scrypt to store you passwords. See here.

您应该使用另一种算法,如 bcrypt、PBKDF2 和 scrypt 来存储密码。见这里

回答by Simon

Here you have two links for MD5 hashing and other hash methods:

这里有两个用于 MD5 散列和其他散列方法的链接:

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

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

教程:http: //www.twmacinta.com/myjava/fast_md5.php

回答by ZZ Coder

Among all the standard hash schemes, LDAP ssha is the most secure one to use,

在所有标准哈希方案中,LDAP ssha 是最安全的一种,

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

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

I would just follow the algorithms specified there and use MessageDigest to do the hash.

我只会按照那里指定的算法并使用 MessageDigest 来进行散列。

You need to store the salt in your database as you suggested.

您需要按照您的建议将盐存储在数据库中。

回答by laz

You can use the Shirolibrary's (formerly JSecurity) implementationof what is described by OWASP.

您可以使用Shiro库(以前称为JSecurity实现OWASP所描述的内容。

It also looks like the JASYPT library has a similar utility.

看起来 JASYPT 库也有类似的实用程序

回答by David Carboni

Fully agree with Erickson that PBKDF2is the answer.

完全同意 Erickson 的观点,即PBKDF2就是答案。

If you don't have that option, or only need to use a hash, Apache Commons DigestUtils is much easier than getting JCE code right: https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html

如果您没有该选项,或者只需要使用哈希,Apache Commons DigestUtils 比正确获取 JCE 代码要容易得多:https://commons.apache.org/proper/commons-codec/apidocs/org/apache /commons/codec/digest/DigestUtils.html

If you use a hash, go with sha256 or sha512. This page has good recommendations on password handling and hashing (note it doesn't recommend hashing for password handling): http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

如果您使用散列,请使用 sha256 或 sha512。此页面对密码处理和散列有很好的建议(注意它不建议对密码处理进行散列):http: //www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html

回答by Martin Konicek

Here is a complete implementationwith two methods doing exactly what you want:

这是一个完整的实现,有两种方法完全符合您的要求:

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

The point is that even if an attacker gets access to both your database and source code, the passwords are still safe.

关键是即使攻击者可以访问您的数据库和源代码,密码仍然是安全的。

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

We are storing 'salt$iterated_hash(password, salt)'. The salt are 32 random bytes and it's purpose is that if two different people choose the same password, the stored passwords will still look different.

我们正在存储'salt$iterated_hash(password, salt)'. salt 是 32 个随机字节,其目的是如果两个不同的人选择相同的密码,存储的密码看起来仍然不同。

The iterated_hash, which is basically hash(hash(hash(... hash(password, salt) ...)))makes it very expensive for a potential attacker who has access to your database to guess passwords, hash them, and look up hashes in the database. You have to compute this iterated_hashevery time a user logs in, but it doesn't cost you that much compared to the attacker who spends nearly 100% of their time computing hashes.

iterated_hash,这基本上是hash(hash(hash(... hash(password, salt) ...)))使得对于谁可以访问你的数据库来猜测密码,哈希他们潜在的攻击者很昂贵,并查找散列在数据库中。iterated_hash每次用户登录时您都必须计算它,但与花费近 100% 的时间计算哈希的攻击者相比,它不会花费太多。

回答by Eran Medan

In addition to bcrypt and PBKDF2 mentioned in other answers, I would recommend looking at scrypt

除了其他答案中提到的 bcrypt 和 PBKDF2 之外,我还建议您查看scrypt

MD5 and SHA-1 are not recommended as they are relatively fast thus using "rent per hour" distributed computing (e.g. EC2) or a modern high end GPU one can "crack" passwords using brute force / dictionary attacks in relatively low costs and reasonable time.

不推荐使用 MD5 和 SHA-1,因为它们相对较快,因此使用“每小时租金”分布式计算(例如 EC2)或现代高端 GPU 可以以相对较低的成本和合理的价格使用蛮力/字典攻击“破解”密码时间。

If you must use them, then at least iterate the algorithm a predefined significant amount of times (1000+).

如果您必须使用它们,那么至少将算法迭代预定义的大量次数(1000 次以上)。

回答by Qw3ry

While the NIST recommendation PBKDF2has already been mentioned, I'd like to point out that there was a public password hashing competitionthat ran from 2013 to 2015. In the end, Argon2was chosen as the recommended password hashing function.

虽然已经提到了NIST 推荐的 PBKDF2,但我想指出的是,从 2013 年到 2015 年有一场公开的密码哈希竞赛。最后,Argon2被选为推荐的密码哈希函数。

There is a fairly well adopted Java bindingfor the original (native C) library that you can use.

对于您可以使用的原始(本机 C)库,有一个相当不错的Java 绑定

In the average use-case, I don't think it does matter from a security perspective if you choose PBKDF2 over Argon2 or vice-versa. If you have strong security requirements, I recommend considering Argon2 in your evaluation.

在一般用例中,如果您选择 PBKDF2 而不是 Argon2,我认为从安全角度来看这并不重要,反之亦然。如果您有很强的安全要求,我建议您在评估中考虑 Argon2。

For further information on the security of password hashing functions see security.se.

有关密码散列函数安全性的更多信息,请参阅security.se