概述在编程开发中,用户登录、注册等功能是很常见的,那么对于用户密码的处理我们应该选择什么样的加密算法呢?在这种场景下,算法需要满足以下两个条件:算法需要不可逆,才能有效防止密码泄露。算法需要比较慢,计算代价可以动态调整。Slowness是应对暴力破解的有效方法。目前有几种算法PBKDF2、BCrypt和SCrypt可以满足。我们先看看老密码的加密方式。旧加密过去,密码加密使用MD5或SHA。MD5是早期设计的加密哈希。它非常快速地生成哈希。随着计算机能力的增强,已经被破解,所以出现了一些长度增加的散列函数,如:SHA-1,SHA-256等。这里有一些比较:MD5:FastGeneratesshorthashes(16bytes).发生意外碰撞的概率大约为:\(1.47\times10^{-29}\)。SHA1:比md5慢20%,产生比MD5稍长的散列(20字节)。发生意外碰撞的概率大约为:\(1\times10^{-45}\)。SHA256:最慢,通常比md5慢60%,并生成长散列(32字节)。发生意外碰撞的概率大约为:\(4.3\times10^{-60}\)。为了保证安全,可以选择SHA-512,目前最长的hash,但是硬件能力越来越强,可能有一天会发现新的漏洞,研究人员会推出更新的版本,新版本的长度会更长。越来越长了,他们也可能会公布底层算法,所以我们应该单独寻找更合适的算法。除了选择足够可靠的加密算法外,还必须增加输入数据的强度,以保证加盐密码的安全性,因为密码是人为设置的,字符长度和强度的组合不能相同。直接哈希存储往往会提高爆破概率,这时我们需要加盐。Salting是密码学中经常提到的一个概念,其实就是随机数据。以下是java生成盐的示例:publicstaticbyte[]generateSalt(){SecureRandomrandom=newSecureRandom();byte[]salt=newbyte[16];random.nextBytes(盐);返回盐;}SHA-512加盐哈希密码publicstaticStringsha512(StringrawPassword,byte[]salt){try{MessageDigestmd=MessageDigest.getInstance("SHA-512");//添加一些盐md.update(salt);返回十六进制.encodeHexString(md.digest(rawPassword.getBytes(StandardCharsets.UTF_8)));}catch(GeneralSecurityExceptionex){thrownewIllegalStateException("无法创建散列",ex);}}PBKDF2PBKDF1和PBKDF2是一个密钥推导函数,其作用是根据指定的密码生成加密密钥。之前在通用加密算法中提到过。虽然不是加密的哈希函数,但是因为有足够的安全性,所以还是适合密码存储的场景。PBKDF2函数计算如下:$$DK=PBKDF2(PRF,Password,Salt,Iterations,HashWidth)$$\(PRF\)是一个伪随机函数,有两个参数输出固定长度(例如HMAC);\(Password\)是生成派生密钥的主密码;\(Salt\)是加密盐;\(Iterations\)是迭代次数,次数越多;\(HashWidth\)是派生密钥的长度;\(DK\)是生成的派生密钥。PRF(HMAC)大致是一个迭代过程。第一次将Password作为key和Salt传入,然后将输出结果作为输入重复后面的迭代。HMAC:基于散列的消息认证代码,可以使用共享密钥提供认证。比如HMAC-SHA256,输入要认证的报文和key进行计算,然后输出sha256的hash值。PBKDF2不同于MD和SHA哈希函数,它通过增加迭代次数来增加破解难度,并且还可以根据情况进行配置,这使得它具有滑动计算成本。对于MD5和SHA,攻击者每秒可以猜出数十亿个密码。使用PBKDF2,攻击者每秒只能进行几千次猜测(或更少,取决于配置),因此适合对抗暴力攻击。在2021年,OWASP建议对PBKDF2-HMAC-SHA256使用310000次迭代,对PBKDF2-HMAC-SHA512使用120000次迭代publicstaticStringpbkdf2Encode(StringrawPassword,byte[]salt){try{intiterations=310000;inthashWidth=256;PBEKeySpecspec=newPBEKeySpec(rawPassword.toCharArray(),salt,iterations,hashWidth);SecretKeyFactoryskf=SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");返回Base64.getEncoder().encodeToString(skf.get.generateSecret(编码)());}catch(GeneralSecurityExceptionex){thrownewIllegalStateException("无法创建散列",ex);}}Bcrypt简介bcrypt是一个基于eksblowfish算法设计的加密哈希函数。它最大的特点是可以动态调整工作因子(迭代次数)来调整计算速度,这样即使未来计算机算力不断增加,它仍然可以抵抗暴力破解攻击。eksblowfish算法,采用块加密方式,支持密钥计算代价(迭代次数)动态设置。算法的详细介绍可以参考以下文章:https://www.usenix.org/legacy...结构体bcrypt函数输入的密码字符串不超过72字节,包括算法标识,a计算成本和一个16字符的字符串部分(128位)盐值。输入计算得到一个24字节(192位)的哈希,最终输出格式如下:$2a$12$DQoa2eT/aXFPgIoGwfllHuj4wEA3F71WWT7E/Trez331HGDUSRvXi\__/\/\__________________/\___________________________/AlgCostSaltHash$2a$:bcrypt算法标识12:工作因子(2^12表示4096次迭代)DQoa2eT/aXFPgIoGwfllHu:base64盐值;j4wEA3F71WWT7E/Trez331HGDUSRvXi:计算的Base64哈希值(24字节)。bcryptversion$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的关键是设置一个合适的工作因子。理想的工作因素没有特定的规则。它主要取决于服务器的性能和应用程序上的用户数量。通常,它被设置为安全性和应用程序性能之间的权衡。如果你的factor设置的比较高,虽然可以保证攻击者难以破解hash,但是登录验证也会变慢,严重影响用户体验,也有可能被攻击者用来执行通过大量登录尝试攻击耗尽服务器CPU的拒绝服务。一般来说,计算哈希的时间不应超过一秒。我们使用springsecurityBCryptPasswordEncoder来查看不同因素下的哈希生成时间。我的电脑配置如下:处理器:2.2GHz四核IntelCorei7内存:16GB1600MHzDDR3显卡:IntelIrisPro1536MBMap
