Hashing a Password in Java – 在Java中哈希一个密码

最后修改: 2018年 9月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll be discussing the importance of password hashing.

在本教程中,我们将讨论密码散列的重要性。

We’ll take a quick look at what it is, why it’s important, and some secure and insecure ways of doing it in Java.

我们将快速了解一下什么是安全,为什么重要,以及在Java中的一些安全和不安全的方法。

2. What’s Hashing?

2.什么是哈希?

Hashing is the process of generating a string, or hash, from a given message using a mathematical function known as a cryptographic hash function.

哈希是使用被称为加密哈希函数的数学函数从给定的信息生成一个字符串,或哈希的过程。

While there are several hash functions out there, those tailored to hashing passwords need to have four main properties to be secure:

虽然有几个散列函数,但那些专门用于散列密码的函数需要有四个主要属性才是安全的。

  1. It should be deterministic: the same message processed by the same hash function should always produce the same hash
  2. It’s not reversible: it’s impractical to generate a message from its hash
  3. It has high entropy: a small change to a message should produce a vastly different hash
  4. And it resists collisions: two different messages should not produce the same hash

A hash function that has all four properties is a strong candidate for password hashing since together they dramatically increase the difficulty in reverse-engineering the password from the hash.

具有这四种特性的散列函数是密码散列的有力候选者,因为它们共同极大地增加了从散列中反向生成密码的难度。

Also, though, password hashing functions should be slow. A fast algorithm would aid brute force attacks in which a hacker will attempt to guess a password by hashing and comparing billions (or trillions) of potential passwords per second.

此外,密码散列函数应该很慢。快速的算法将有助于brute force 攻击,在这种攻击中,黑客将试图通过每秒散列和比较数十亿(或数万亿)的潜在密码来猜测一个密码。

Some great hash functions that meet all these criteria are PBKDF2, BCrypt, and SCrypt. But first, let’s take a look at some older algorithms and why they are no longer recommended

一些符合所有这些标准的伟大的哈希函数是PBKDF2,BCrypt,SCrypt。但首先,让我们看看一些旧的算法,以及为什么不再推荐它们

3. Not Recommended: MD5

3.不推荐 MD5

Our first hash function is the MD5 message-digest algorithm, developed way back in 1992.

我们的第一个哈希函数是MD5消息加密算法,该算法早在1992年就已开发。

Java’s MessageDigest makes this easy to calculate and can still be useful in other circumstances.

Java的MessageDigest使得这一点很容易计算,并且在其他情况下仍然可以发挥作用。

However, over the last several years, MD5 was discovered to fail the fourth password hashing property in that it became computationally easy to generate collisions. To top it off, MD5 is a fast algorithm and therefore useless against brute-force attacks.

然而,在过去几年中,MD5被发现违反了第四个密码散列属性,因为它在计算上变得容易产生碰撞。更重要的是,MD5是一种快速算法,因此对暴力攻击毫无用处。

Because of these, MD5 is not recommended.

由于这些原因,不推荐使用MD5。

4. Not Recommended: SHA-512

4.不推荐 SHA-512

Next, we’ll look at SHA-512, which is part of the Secure Hash Algorithm family, a family that began with SHA-0 back in 1993.

接下来,我们来看看SHA-512,它是安全哈希算法家族的一部分,这个家族早在1993年就开始了SHA-0。

4.1. Why SHA-512?

4.1.为什么是SHA-512?

As computers increase in power, and as we find new vulnerabilities, then researchers derive new versions of SHA. Newer versions have a progressively longer length, or sometimes researchers publish a new version of the underlying algorithm.

随着计算机功率的增加,以及我们发现新的漏洞,那么研究人员就会推导出SHA的新版本。新版本的长度逐渐变长,或者有时研究人员会发布一个新版本的基础算法。

SHA-512 represents the longest key in the third generation of the algorithm.

SHA-512代表第三代算法中最长的密钥。

While there are now more secure versions of SHA, SHA-512 is the strongest that is implemented in Java.

虽然现在有更安全的SHA版本,但SHA-512是在Java中实现的最强大的

4.2. Implementing in Java

4.2.在Java中实现

Now, let’s have a look at implementing the SHA-512 hashing algorithm in Java.

现在,让我们来看看如何在Java中实现SHA-512散列算法。

First, we have to understand the concept of salt. Simply put, this is a random sequence that is generated for each new hash.

首先,我们必须了解salt的概念。简单地说,这是一个随机序列,为每个新的哈希值生成

By introducing this randomness, we increase the hash’s entropy, and we protect our database against pre-compiled lists of hashes known as rainbow tables.

通过引入这种随机性,我们增加了哈希的,并保护我们的数据库免受预先编译的哈希列表的影响,这些列表被称为彩虹表

Our new hash function then becomes roughly:

我们的新哈希函数就变成了大致的。

salt <- generate-salt;
hash <- salt + ':' + sha512(salt + password)

4.3. Generating a Salt

4.3.生成一个盐

To introduce salt, we’ll use the SecureRandom class from java.security:

为了引入盐,我们将使用SecureRandom类,它来自java.security

SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);

Then, we’ll use the MessageDigest class to configure the SHA-512 hash function with our salt:

然后,我们将使用MessageDigest类,用我们的盐配置SHA-512哈希函数。

MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt);

And with that added, we can now use the digest method to generate our hashed password:

有了这个补充,我们现在可以使用digest方法来生成我们的散列密码。

byte[] hashedPassword = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));

4.4. Why Is It Not Recommended?

4.4.为什么不推荐?

When employed with salt, SHA-512 is still a fair option, but there are stronger and slower options out there.

当与盐一起使用时,SHA-512仍然是一个公平的选择但还有更强、更慢的选择

Also, the remaining options we’ll cover have an important feature: configurable strength.

另外,我们要介绍的其余选项有一个重要的特点:可配置的强度。

5. PBKDF2, BCrypt, and SCrypt

5.PBKDF2、BCrypt和SCrypt

PBKDF2, BCrypt, and SCrypt are three recommended algorithms.

PBKDF2、BCrypt和SCrypt是三种推荐算法。

5.1. Why Are Those Recommended?

5.1.为什么推荐这些?

Each of these is slow, and each has the brilliant feature of having a configurable strength.

每一个都很慢,而且每一个都有一个出色的特点,即有一个可配置的强度。

This means that as computers increase in strength, we can slow down the algorithm by changing the inputs.

这意味着,随着计算机实力的增强,我们可以通过改变输入来减缓算法的速度。

5.2. Implementing PBKDF2 in Java

5.2.在Java中实现PBKDF2

Now, salts are a fundamental principle of password hashing, and so we need one for PBKDF2, too:

现在,salts是密码散列的一个基本原则,所以我们也需要一个PBKDF2。

SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);

Next, we’ll create a PBEKeySpec and a SecretKeyFactory which we’ll instantiate using the PBKDF2WithHmacSHA1 algorithm:

接下来,我们将创建一个PBEKeySpec和一个SecretKeyFactory,我们将使用PBKDF2WithHmacSHA1算法进行实例化。

KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

The third parameter (65536) is effectively the strength parameter. It indicates how many iterations that this algorithm run for, increasing the time it takes to produce the hash.

第三个参数(65536)实际上是强度参数。它表示该算法运行的迭代次数,增加产生哈希值的时间。

Finally, we can use our SecretKeyFactory to generate the hash:

最后,我们可以使用我们的SecretKeyFactory来生成哈希值。

byte[] hash = factory.generateSecret(spec).getEncoded();

5.3. Implementing BCrypt and SCrypt in Java

5.3.在Java中实现BCrypt和SCrypt

So, it turns out that BCrypt and SCrypt support don’t yet ship with Java, though some Java libraries support them.

因此,事实证明,BCrypt和SCrypt支持尚未与Java一起发货,尽管一些Java库支持它们。

One of those libraries is Spring Security.

这些库中的一个是Spring Security。

6. Password Hashing With Spring Security

6.使用Spring Security的密码哈希

Although Java natively supports both the PBKDF2 and SHA hashing algorithms, it doesn’t support BCrypt and SCrypt algorithms.

尽管Java原生支持PBKDF2和SHA散列算法,但它并不支持BCrypt和SCrypt算法。

Luckily for us, Spring Security ships with support for all these recommended algorithms via the PasswordEncoder interface:

幸运的是,Spring Security通过PasswordEncoder接口提供了对所有这些推荐算法的支持。

  • Pbkdf2PasswordEncoder gives us PBKDF2
  • BCryptPasswordEncoder gives us BCrypt, and
  • SCryptPasswordEncoder gives us SCrypt

The password encoders for PBKDF2, BCrypt, and SCrypt all come with support for configuring the desired strength of the password hash.

PBKDF2、BCrypt和SCrypt的密码编码器都支持配置所需的密码哈希强度。

We can use these encoders directly, even without having a Spring Security-based application. Or, if we are protecting our site with Spring Security, then we can configure our desired password encoder through its DSL or via dependency injection.

我们可以直接使用这些编码器,即使没有一个基于Spring Security的应用程序。或者,如果我们使用Spring Security保护我们的网站,那么我们可以通过其DSL或通过依赖注入来配置我们所需的密码编码器。

And, unlike our examples above, these encryption algorithms will generate the salt for us internally. The algorithm stores the salt within the output hash for later use in validating a password.

而且,与我们上面的例子不同,这些加密算法将在内部为我们生成盐。该算法将盐存储在输出的哈希值中,以便以后用于验证密码。

7. Conclusion

7.结论

So, we’ve taken a deep dive into password hashing; exploring the concept and its uses.

因此,我们对密码散列进行了深入研究;探索了这个概念和它的用途。

And we’ve taken a look at some historical hash functions as well as some currently implemented ones before coding them in Java.

在用Java编码之前,我们已经看了一些历史上的哈希函数,以及一些目前实现的哈希函数。

Finally, we saw that Spring Security ships with its password encrypting classes, implementing an array of different hash functions.

最后,我们看到Spring Security提供了密码加密类,实现了一系列不同的哈希函数。

As always, the code is available on GitHub.

一如既往,代码是可在GitHub.上获得。