存储用户密码应该使用什么加密算法?

您所在的位置:网站首页 不可逆的加密方法 存储用户密码应该使用什么加密算法?

存储用户密码应该使用什么加密算法?

2024-07-12 04:00| 来源: 网络整理| 查看: 265

概述

编程开发中,像用户登录注册这种功能很常见,那么对于用户密码处理,我们该选择什么样的加密算法呢?在这种场景下,算法需要满足下面两个条件:

算法需不可逆,这样才能有效防止密码泄露。 算法需相对慢,可以动态调整计算成本,缓慢是应对暴力破解有效方式。

目前来看有这么几个算法 PBKDF2、 BCrypt 和 SCrypt 可以满足。我们先看下旧的密码加密方式。

旧的加密

过去密码加密常用MD5或者SHA。MD5是早期设计的加密哈希,它生成哈希速度很快,随着计算机能力的增强,出现了被破解的情况,所以又有了一些长度增大的哈希函数,如:SHA-1,SHA-256等。下面是它们的一些比较:

MD5:速度快生成短哈希(16 字节)。意外碰撞的概率约为:\(1.47 \times 10^{-29}\) 。

SHA1:比 md5 慢 20%,生成的哈希比 MD5 长一点(20 字节)。意外碰撞的概率约为:\(1 \times 10^{-45}\)。

SHA256:最慢,通常比 md5 慢 60%,并且生成的哈希长(32 字节)。意外碰撞的概率约为:\(4.3 \times 10^{-60}\) 。

为了确保安全你可能会选择目前长度最长的哈希SHA-512,但硬件能力在增强,或许有一天又会发现新的漏洞,研究人员又推出较新的版本,新版本的长度也会越来越长,而且他们也可能会发布底层算法,所以我们应该另外寻找更合适的算法。

加盐操作

密码安全,除了要选择足够可靠的加密算法外,输入数据的强度也要提升,因为密码是人设置的,其字符长度组合强度不可能一致,如果直接进行哈希存储往往会提升爆破的概率,这时我们需要加盐。

加盐是密码学中经常提到的概念,其实就是随机数据。下面是一个 java 中生成盐的例子:

public static byte[] generateSalt() { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); return salt; }

SHA-512 加盐哈希密码

public static String sha512(String rawPassword, byte[] salt) { try { MessageDigest md = MessageDigest.getInstance("SHA-512"); // 加点盐 md.update(salt); return Hex.encodeHexString(md.digest(rawPassword.getBytes(StandardCharsets.UTF_8))); } catch (GeneralSecurityException ex) { throw new IllegalStateException("Could not create hash", ex); } } PBKDF2

PBKDF1和PBKDF2是一个密钥派生函数,其作用就是根据指定的密码短语生成加密密钥。之前在 常见加密算法 提到过。它虽然不是加密哈希函数,但它仍然适用密码存储场景,因为它有足够的安全性,PBKDF2 函数计算如下:

\[DK = PBKDF2(PRF, Password, Salt, Iterations, HashWidth) \]

\(PRF\) 是伪随机函数两个参数,输出固定的长度(例如,HMAC); \(Password\) 是生成派生密钥的主密码; \(Salt\) 是加密盐; \(Iterations\) 是迭代次数,次数越多; \(HashWidth\) 是派生密钥的长度; \(DK\) 是生成的派生密钥。

PRF(HMAC)大致迭代过程,第一次时将 Password 作为密钥和Salt传入,然后再将输出结果作为输入重复完成后面迭代。

PBKDF2

HMAC:基于哈希的消息认证码,可以使用共享密钥提供身份验证。比如HMAC-SHA256,输入需要认证的消息和密钥进行计算,然后输出sha256的哈希值。

PBKDF2不同于MD和SHA哈希函数,它通过增加迭代次数提升了破解难度,并且还可以根据情况进行配置,这使得它具有滑动计算成本。

对于MD5和SHA,攻击者每秒可以猜测数十亿个密码。而使用 PBKDF2,攻击者每秒只能进行几千次猜测(或更少,取决于配置),所以它适用于抗击暴力攻击。

2021 年,OWASP 建议对 PBKDF2-HMAC-SHA256 使用 310000 次迭代,对 PBKDF2-HMAC-SHA512 使用 120000 次迭代

public static String pbkdf2Encode(String rawPassword, byte[] salt) { try { int iterations = 310000; int hashWidth = 256; PBEKeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, hashWidth); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); return Base64.getEncoder().encodeToString(skf.generateSecret(spec).getEncoded()); } catch (GeneralSecurityException ex) { throw new IllegalStateException("Could not create hash", ex); } } Bcrypt 简介

bcrypt 是基于 eksblowfish 算法设计的加密哈希函数,它最大的特点是:可以动态调整工作因子(迭代次数)来调整计算速度,因此就算以后计算机能力不断增加,它仍然可以抵抗暴力攻击。

关于eksblowfish算法,它是采用分组加密模式,并且支持动态设定密钥计算成本(迭代次数)。算法的详细介绍可查看下面文章:

https://www.usenix.org/legacy/publications/library/proceedings/usenix99/full_papers/provos/provos_html/node4.html

结构

bcrypt 函数输入的密码字符串不超过 72 个字节、包含算法标识符、一个计算成本和一个 16 字节(128 位)的盐值。通过输入计算得到 24字节(192位)哈希,最终输出格式如下:

$2a$12$DQoa2eT/aXFPgIoGwfllHuj4wEA3F71WWT7E/Trez331HGDUSRvXi \__/\/ \____________________/\_____________________________/ Alg Cost Salt Hash $2a$: bcrypt 算法标识符或叫版本; 12: 工作因子 (2^12 表示 4096 次迭代) DQoa2eT/aXFPgIoGwfllHu: base64 的盐值; j4wEA3F71WWT7E/Trez331HGDUSRvXi: 计算后的 Base64 哈希值(24 字节)。

bcrypt 版本

$2a$: 规定哈希字符串必须是 UTF-8 编码,必须包含空终止符。 $2y$: 该版本为修复 2011年6月 PHP 在 bcrypt 实现中的一个错误。 $2b$: 该版本为修复 2014年2月 OpenBSD 在 bcrypt 实现中的一个错误。

2014年2月 在 OpenBSD 的 bcrypt 实现中发现,它使用一个无符号的 8 位值来保存密码的长度。对于长度超过255字节的密码,密码将在72或长度模256 中的较小者处被截断,而不是被截断为72字节。例如:260 字节的密码将被截断为4个字节,而不是截断为 72 个字节。

实践

bcrypt 关键在于设定合适的工作因子,理想的工作因子没有特定的法则,主要还是取决于服务器的性能和应用程序上的用户数量,一般在安全性和应用性能之间权衡设定。

假如你的因子设置比较高,虽然可以保证攻击者难以破解哈希,但是登录验证也会变慢,严重影响用户体验,而且也可能被攻击者通过大量登录尝试耗尽服务器的 CPU 来执行拒绝服务攻击。一般来说计算哈希的时间不应该超过一秒。

我们使用 spring security BCryptPasswordEncoder 看下不同因子下产生哈希的时间,我电脑配置如下:

处理器:2.2 GHz 四核Intel Core i7 内存:16 GB 1600 MHz DDR3 显卡:Intel Iris Pro 1536 MB

Map encoderMap = new LinkedHashMap(); for (int i = 8; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3