如何在Java中为Salted-Hash生成SALT?

Lou*_*ong 41 java security encryption hash salt

我一直在寻找,最接近的答案是:如何生成一个随机的字母数字字符串?

我想根据这个CrackStation教程来遵循这个工作流程:

存储密码

  1. 使用CSPRNG生成长的随机盐.

  2. 将salt添加到密码中,并使用标准加密哈希函数(如SHA256)对其进行哈希处理.

  3. 将salt和hash都保存在用户的数据库记录中.

验证密码

  1. 从数据库中检索用户的salt和hash.

  2. 将salt添加到给定密码并使用相同的哈希函数对其进行哈希处理.

  3. 将给定密码的哈希值与数据库中的哈希值进行比较.如果匹配,则密码正确.否则,密码不正确.

我不知道如何生成SALT.我想出了如何使用MessageDigest生成哈希.我尝试使用SecureRandom,但nextByte方法产生乱码.

编辑:我不知道选择哪个答案,对我来说太复杂了,我决定使用jBCrypt; jBCript很容易使用,在幕后做所有复杂的事情.所以我会让社区投票给出最佳答案.

ass*_*ias 51

受到这篇文章帖子的启发,我使用此代码生成并验证散列的盐渍密码.它只使用JDK提供的类,没有外部依赖.

过程是:

  • 你创造了一个盐 getNextSalt
  • 您询问用户他的密码并使用该hash方法生成盐渍和散列密码.该方法返回一个byte[]可以使用salt保存在数据库中的方法
  • 要对用户进行身份验证,您需要输入密码,从数据库中检索salt和哈希密码,并使用该isExpectedPassword方法检查详细信息是否匹配
/**
 * A utility class to hash passwords and check passwords vs hashed values. It uses a combination of hashing and unique
 * salt. The algorithm used is PBKDF2WithHmacSHA1 which, although not the best for hashing password (vs. bcrypt) is
 * still considered robust and <a href="https://security.stackexchange.com/a/6415/12614"> recommended by NIST </a>.
 * The hashed value has 256 bits.
 */
public class Passwords {

  private static final Random RANDOM = new SecureRandom();
  private static final int ITERATIONS = 10000;
  private static final int KEY_LENGTH = 256;

  /**
   * static utility class
   */
  private Passwords() { }

  /**
   * Returns a random salt to be used to hash a password.
   *
   * @return a 16 bytes random salt
   */
  public static byte[] getNextSalt() {
    byte[] salt = new byte[16];
    RANDOM.nextBytes(salt);
    return salt;
  }

  /**
   * Returns a salted and hashed password using the provided hash.<br>
   * Note - side effect: the password is destroyed (the char[] is filled with zeros)
   *
   * @param password the password to be hashed
   * @param salt     a 16 bytes salt, ideally obtained with the getNextSalt method
   *
   * @return the hashed password with a pinch of salt
   */
  public static byte[] hash(char[] password, byte[] salt) {
    PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
    Arrays.fill(password, Character.MIN_VALUE);
    try {
      SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
      return skf.generateSecret(spec).getEncoded();
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
      throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);
    } finally {
      spec.clearPassword();
    }
  }

  /**
   * Returns true if the given password and salt match the hashed value, false otherwise.<br>
   * Note - side effect: the password is destroyed (the char[] is filled with zeros)
   *
   * @param password     the password to check
   * @param salt         the salt used to hash the password
   * @param expectedHash the expected hashed value of the password
   *
   * @return true if the given password and salt match the hashed value, false otherwise
   */
  public static boolean isExpectedPassword(char[] password, byte[] salt, byte[] expectedHash) {
    byte[] pwdHash = hash(password, salt);
    Arrays.fill(password, Character.MIN_VALUE);
    if (pwdHash.length != expectedHash.length) return false;
    for (int i = 0; i < pwdHash.length; i++) {
      if (pwdHash[i] != expectedHash[i]) return false;
    }
    return true;
  }

  /**
   * Generates a random password of a given length, using letters and digits.
   *
   * @param length the length of the password
   *
   * @return a random password
   */
  public static String generateRandomPassword(int length) {
    StringBuilder sb = new StringBuilder(length);
    for (int i = 0; i < length; i++) {
      int c = RANDOM.nextInt(62);
      if (c <= 9) {
        sb.append(String.valueOf(c));
      } else if (c < 36) {
        sb.append((char) ('a' + c - 10));
      } else {
        sb.append((char) ('A' + c - 36));
      }
    }
    return sb.toString();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @assylias首先,谢谢!两个问题 - 1:2017年仍然相关吗?(libsodium/Argon2?)2:为什么地球上没有apache commons库,因为开发人员和每个具有任何形式的身份验证需求的应用程序通常都会出错?:) (5认同)

Rau*_*wal 11

关于你想要生成盐的方式你是对的,即它只是一个随机数.对于这种特殊情况,它可以保护您的系统免受可能的字典攻击.现在,对于第二个问题,您可以做的不是使用UTF-8编码,而是可能需要使用Base64.这里是用于生成散列的示例.我正在使用Apache Common Codecs来执行base64编码,您可以选择自己的编码

public byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte bytes[] = new byte[20];
        random.nextBytes(bytes);
        return bytes;
    }

public String bytetoString(byte[] input) {
        return org.apache.commons.codec.binary.Base64.encodeBase64String(input);
    }

public byte[] getHashWithSalt(String input, HashingTechqniue technique, byte[] salt) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance(technique.value);
        digest.reset();
        digest.update(salt);
        byte[] hashedBytes = digest.digest(stringToByte(input));
        return hashedBytes;
    }
public byte[] stringToByte(String input) {
        if (Base64.isBase64(input)) {
            return Base64.decodeBase64(input);

        } else {
            return Base64.encodeBase64(input.getBytes());
        }
    }
Run Code Online (Sandbox Code Playgroud)

以下是直接来自OWASP的密码哈希标准实践的一些额外参考