当前位置: 首页 > 后端技术 > Java

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

时间:2023-04-01 22:16:13 Java

概述在编程开发中,用户登录、注册等功能是很常见的,那么对于用户密码的处理我们应该选择什么样的加密算法呢?在这种场景下,算法需要满足以下两个条件:算法需要不可逆,才能有效防止密码泄露。算法需要比较慢,计算代价可以动态调整。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显卡:IntelIrisPro1536MBMapencoderMap=newLinkedHashMap<>();for(inti=8;i<=21;i++){encoderMap.put(i,newBCryptPasswordEncoder(i));}StringplainTextPassword="huhdfJ*!4";for(inti:encoderMap.keySet()){BCryptPasswordEncoder编码器=encoderMap.get(i);长启动=System.currentTimeMillis();encoder.encode(纯文本密码);长端=System.currentTimeMillis();System.out.println(String.format("bcrypt|cost:%d,time:%dms",i,end-start));}加密|成本:8,时间:39msbcrypt|成本:9,时间:45msbcrypt|成本:10,时间:89msbcrypt|成本:11,时间:195msbcrypt|成本:12,时间:376msbcrypt|成本:13,时间:720msbcrypt|成本:14,时间:1430msbcrypt|成本:15,时间:2809msbcrypt|成本:16,时间:5351msbcrypt|成本:17,时间:10737msbcrypt|成本:18,时间:21417msbcrypt|成本:19,时间:43789msbcrypt|成本:288723msbcrypt|cost:21,time:176704ms拟合得到如下公式:$$10.3064\cdote^{0.696464x}$$BCryptPasswordEncoder因子范围4-31,默认10,我们来推导一下31需要多长时间根据公式/***@paramstrength要使用的日志轮次,在4到31之间*/publicBCryptPasswordEncoder(intstrength){this(strength,null);}$$10.3064\cdote^{0.696464(31)}=24529665567.08815$$在工作因子为31时大约需要284天,因此我们知道使用bcrypt可以轻松扩展哈希计算过程以适应更快的硬件,让我们有很大的余地来防止攻击者进行未来的技术改进。益处。SCryptSCrypt比上述算法出现得晚,是ColinPercival于2009年3月创建的基于密码的密钥推导函数。我们需要了解关于该算法的以下两点:该算法专门用于执行大规模自定义通过需要大量内存进行硬件攻击,这是昂贵的。与上述PBKDF2属于同一类密钥推导函数。Springsecurity也实现了算法SCryptPasswordEncoder,入参如下:CpuCost:算法的cpucost。必须是大于1的2的幂。当前默认值为16,384或2^14)MemoryCost:算法的内存成本。目前默认为8。并行化:算法的并行化目前默认为1。请注意,此实现目前并未利用并行化。KeyLength:算法的密钥长度。当前默认值为32。SaltLength:盐长度。目前默认是64,不过也有人提到,生产系统不建议用它来存储密码。他的结论是,首先,SCrypt是为密钥推导函数而不是密码哈希而设计的,它的实现并不是那么完美。有关详细信息,请参阅下面的文章。https://blog.ircmaxell.com/20...结论我建议使用bcrypt。为什么加密?在密码存储的场景中,密码散列是最好的方式。首先,它是一个加密的哈希函数。其次,根据摩尔定律的定义,集成系统上每平方英寸的晶体管数量每18个月就会翻一番。一些。在2年内,我们可以增加它的工作因数以适应任何变化。当然,这并不是说其他??算法不够安全,你还是可以选择其他算法。建议先用bcrypt,再用密钥派生类(PBKDF2和SCrypt),最后用hash+salt(SHA256(salt))。