在本教程中,我们将讨论密码哈希的重要性。
我们将快速了解它是什么,为什么它很重要,以及在 Java 中执行它的一些安全和不安全的方法。
散列是使用称为加密散列函数的数学函数从给定消息生成字符串或散列的过程。
虽然有几个散列函数,但那些为散列密码量身定制的函数需要具有四个主要属性才能确保安全:
具有所有四个属性的哈希函数是密码哈希的有力候选者,因为它们一起极大地增加了从哈希中逆向工程密码的难度。
不过,密码散列函数也应该很慢。快速算法将有助于暴力 攻击,其中黑客将尝试通过散列和比较每秒数十亿(或数万亿)个潜在密码来猜测密码。
满足所有这些标准的一些出色的散列函数是PBKDF2、 BCrypt 和 SCrypt。 但首先,让我们看一下一些较旧的算法以及为什么不再推荐它们
我们的第一个哈希函数是 MD5 消息摘要算法,开发于 1992 年。
Java 的 MessageDigest使计算变得容易,并且在其他情况下仍然有用。
然而,在过去几年中,发现 MD5在第四个密码散列属性 中失败,因为它在计算上变得容易产生冲突。最重要的是,MD5 是一种快速算法,因此对暴力攻击毫无用处。
因此,不推荐使用 MD5。
接下来,我们将看看 SHA-512,它是 Secure Hash Algorithm 系列的一部分,该系列始于 1993 年的 SHA-0。
随着计算机功能的增加,以及我们发现新的漏洞,研究人员会推导出新版本的 SHA。较新版本的长度越来越长,或者有时研究人员会发布底层算法的新版本。
SHA-512 代表第三代算法中最长的密钥。
虽然现在有更安全的 SHA 版本,但SHA-512 是用 Java 实现的最强版本。
现在,让我们看看在 Java 中实现 SHA-512 哈希算法。
首先,我们要了解 盐的概念。简单地说,这是为每个新哈希生成的随机序列。
通过引入这种随机性,我们增加了散列的熵,并且我们保护我们的数据库免受称为彩虹表的预编译散列列表的影响。
然后我们的新哈希函数大致变为:
- salt <- generate-salt;
- hash <- salt + ':' + sha512(salt + password)
为了引入盐,我们将使用 java.security中的SecureRandom 类:
- SecureRandom random = new SecureRandom();
- byte[] salt = new byte[16];
- random.nextBytes(salt);
然后,我们将使用 MessageDigest 类使用我们的 salt 配置 SHA-512 哈希函数:
- MessageDigest md = MessageDigest.getInstance("SHA-512");
- md.update(salt);
添加后,我们现在可以使用摘要方法生成我们的哈希密码:
byte[] hashedPassword = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
当与 salt 一起使用时,SHA-512 仍然是一个不错的选择,但还有更强大和更慢的选择。
此外,我们将介绍的其余选项具有一个重要特性:可配置强度。
PBKDF2、BCrypt 和 SCrypt 是三种推荐的算法。
这些中的每一个都很慢,并且每个都具有可配置强度的出色功能。
这意味着随着计算机强度的增加, 我们可以通过改变输入来减慢算法的速度。
现在, 盐是密码散列的基本原理,因此我们也需要一个用于 PBKDF2 的:
- SecureRandom random = new SecureRandom();
- byte[] salt = new byte[16];
- random.nextBytes(salt);
接下来,我们将创建一个 PBEKeySpec 和一个SecretKeyFactory ,我们将使用 PBKDF2WithHmacSHA1 算法对其进行实例化:
- KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
第三个参数(65536)实际上是强度参数。它指示该算法运行了多少次迭代,从而增加了生成哈希所需的时间。
最后,我们可以使用我们的 SecretKeyFactory 来生成哈希:
byte[] hash = factory.generateSecret(spec).getEncoded();
因此,事实证明BCrypt 和 SCrypt 支持尚未随 Java 一起提供,尽管一些 Java 库支持它们。
其中一个库是 Spring Security。
尽管 Java 本身支持 PBKDF2 和 SHA 哈希算法,但它不支持 BCrypt 和 SCrypt 算法。
幸运的是,Spring Security 通过PasswordEncoder接口支持所有这些推荐算法 :
PBKDF2、BCrypt 和 SCrypt 的密码编码器都支持配置所需的密码哈希强度。
我们可以直接使用这些编码器,即使没有基于 Spring Security 的应用程序。或者,如果我们使用 Spring Security 保护我们的站点,那么我们可以通过其 DSL 或依赖注入来配置我们想要的密码编码器。
而且,与我们上面的示例不同,这些加密算法将在内部为我们生成盐。该算法将盐存储在输出哈希中,以供以后用于验证密码。
因此,我们深入研究了密码散列;探索这个概念及其用途。
在用 Java 编码之前,我们已经查看了一些历史哈希函数以及一些当前实现的哈希函数。
最后,我们看到 Spring Security 附带了它的密码加密类,实现了一系列不同的哈希函数。
与往常一样,代码可在 GitHub 上找到。