springSecurity之PasswordEncoder
密码存储演进史
自从互联网有了用户的那一刻起,存储用户密码这件事便成为了一个健全的系统不得不面对的一件事。
远古时期,明文存储密码可能还不被认为是一个很大的系统缺陷(事实上这是一件很恐怖的事)。提及明文存储密码,我立刻联想到的是 CSDN 社区在 2011 年末发生的 600 万用户密码泄露的事件,谁也不会想到这个和程序员密切相关的网站会犯如此低级的错误。
明文存储密码使得恶意用户可以通过 sql 注入等攻击方式来获取用户名和密码,虽然安全框架和良好的编码规范可以规避很多类似的攻击,但依旧避免不了系统管理员,DBA 有途径获取用户密码这一事实。事实上,不用明文存储存储密码,程序员们早在 n 多年前就已经达成了共识。
不能明文存储,一些 hash 算法便被广泛用做密码的编码器,对密码进行单向 hash 处理后存储数据库,当用户登录时,计算用户输入的密码的 hash 值,将两者进行比对。单向 hash 算法,顾名思义,它无法(或者用不能轻易更为合适)被反向解析还原出原密码。这杜绝了管理员直接获取密码的途径,可仅仅依赖于普通的 hash 算法(如 md5,sha256)是不合适的,他主要有 3 个特点:
- 同一密码生成的 hash 值一定相同
- 不同密码的生成的 hash 值可能相同(md5 的碰撞问题相比 sha256 还要严重)
- 计算速度快。
以上三点结合在一起,破解此类算法成了不是那么困难的一件事,尤其是第三点,会在下文中再次提到,多快才算非常快?按照相关资料的说法:
modern hardware perform billions of hash calculations a second.
考虑到大多数用户使用的密码多为数字 + 字母 + 特殊符号的组合,攻击者将常用的密码进行枚举,甚至通过排列组合来暴力破解,这被称为 rainbow(彩虹) table。算法爱好者能够立刻看懂到上述的方案,这被亲切地称之为—打表,一种暴力美学,这张表是可以被复用的。
虽然仅仅依赖于传统 hash 算法的思路被否决了,但这种 hash 后比对的思路,几乎被后续所有的优化方案继承。
hash 方案迎来的第一个改造是对引入一个“随机的因子”
来掺杂进明文中进行 hash 计算,这样的随机因子通常被称之为盐 (salt)
。
salt 一般是用户相关的,每个用户持有各自的 salt。此时狗蛋和二丫的密码即使相同,由于 salt 的影响,存储在数据库中的密码也是不同的,除非…为每个用户单独建议一张 rainbow table。很明显 salted hash 相比普通的单向 hash 方案加大了 hacker 攻击的难度。但了解过 GPU 并行计算能力之强大的童鞋,都能够意识到,虽然破解 salted hash 比较麻烦,却并非不可行,勤劳勇敢的安全专家似乎也对这个方案不够满意。
为解决上述 salted hash 仍然存在的问题,一些新型的单向 hash 算法被研究了出来。其中就包括:Bcrypt,PBKDF2,Scrypt,Argon2。为什么这些 hash 算法能保证密码存储的安全性?因为他们足够慢,恰到好处的慢
。这么说不严谨,只是为了给大家留个深刻的映像:慢。这类算法有一个特点,存在一个影响因子
,可以用来控制计算强度,这直接决定了破解密码所需要的资源和时间,直观的体会可以见下图,在一年内破解如下算法所需要的硬件资源花费(折算成美元)
这使得破解成了一件极其困难的事,并且,其中的计算强度因子是可控的,这样,即使未来量子计算机的计算能力爆表,也可以通过其控制计算强度以防破解。注意,普通的验证过程只需要计算一次 hash 计算,使用此类 hash 算法并不会影响到用户体验。
慢 hash 算法真的安全吗?
Bcrypt,Scrypt,PBKDF2 这些慢 hash 算法是目前最为推崇的 password encoding 方式,好奇心驱使我思考了这样一个问题:慢 hash 算法真的安全吗?
我暂时还没有精力仔细去研究他们中每一个算法的具体实现,只能通过一些文章来拾人牙慧,简单看看这几个算法的原理和安全性。
PBKDF2 被设计的很简单,它的基本原理是通过一个伪随机函数(例如 HMAC 函数),把明文和一个盐值作为输入参数,然后按照设置的计算强度因子重复进行运算,并最终产生密钥。这样的重复 hash 已经被认为足够安全,但也有人提出了不同意见,此类算法对于传统的 CPU 来说的确是足够安全,但 GPU 被搬了出来,前文提到过 GPU 的并行计算能力非常强大。
Bcrypt 强大的一点在于,其不仅仅是 CPU 密集型,还是 RAM 密集型!双重的限制因素,导致 GPU,ASIC(专用集成电路)无法应对 Bcrypt 带来的破解困境。
然后…看了 Scrypt 的相关资料之后我才意识到这个坑有多深。一个熟悉又陌生的词出现在了我面前:FPGA(现场可编程逻辑门阵列),这货就比较厉害了。现成的芯片指令结构如传统的 CPU,GPU,ASIC 都无法破解 Bcrypt,但是 FPGA 支持烧录逻辑门(如 AND、OR、XOR、NOT),通过编程的方式烧录指令集的这一特性使得可以定制硬件来破解 Bcrypt。尽管我不认为懂这个技术的人会去想办法破解真正的系统,但,只要这是一个可能性,就总有方法会被发明出来与之对抗。Scrypt 比 Bcrypt 额外考虑到的就是大规模的 自定义硬件攻击 ,从而刻意设计需要大量内存运算。
理论终归是理论,实际上 Bcrypt 算法被发明至今 18 年,使用范围广,且从未因为安全问题而被修改,其有限性是已经被验证过的,相比之下 Scrypt 据我看到的文章显示是 9 年的历史,没有 Bcrypt 使用的广泛。从破解成本和权威性的角度来看,Bcrypt 用作密码编码器是不错的选择。
springSecurity放开权限控制
我们在添加了spring security依赖后,由于springboot的默认配置,所有的地址都被spring security所控制了,
然而我们目前只是需要用到BCrypt密码加密的部分,所以我们要添加一个配置类,配置为所有地址 都可以匿名访问。
package com.tensquare.user.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/**").permitAll() // 对所有的请求都不进行控制.anyRequest().authenticated().and().csrf().disable();}
}
PasswordEncoder的实现类
以下springSecurity提供的各种加密方式的实现类,可以通过@Bean
注入到spring容器中使用。
// NoOpPasswordEncoder类是明文,只能出现在demo中
@Bean
PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();
}
DelegatingPasswordEncoder
在 spring security 5 提供了这样一个思路,应该将密码编码之后的 hash 值和加密方式一起存储,并提供了一个 DelegatingPasswordEncoder
来作为众多密码密码编码方式的集合。
不用担心密码泄露后,{bcrypt},{pbkdf2},{scrypt},{sha256} 此类前缀会直接暴露密码的编码方式?
其实这个考虑是多余的,因为密码存储的依赖算法并不是一个秘密。大多数能搞到你密码的 hacker 都可以轻松的知道你用的是什么算法,例如,bcrypt 算法通常以 $2a$
开头
// 负责生产 DelegatingPasswordEncoder 的工厂方法
// 默认是bcrypt
public class PasswordEncoderFactories {public static PasswordEncoder createDelegatingPasswordEncoder() {String encodingId = "bcrypt";Map<String, PasswordEncoder> encoders = new HashMap<>();encoders.put(encodingId, new BCryptPasswordEncoder());encoders.put("ldap", new LdapShaPasswordEncoder());encoders.put("MD4", new Md4PasswordEncoder());encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));encoders.put("noop", NoOpPasswordEncoder.getInstance());encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());encoders.put("scrypt", new SCryptPasswordEncoder());encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));encoders.put("sha256", new StandardPasswordEncoder());return new DelegatingPasswordEncoder(encodingId, encoders);}private PasswordEncoderFactories(){}
}
如此注入 PasswordEncoder 之后,我们在数据库中需要这么存储数据:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
事实上DelegatingPasswordEncoder
并不是传统意义上的编码器,它并不使用某一特定算法进行编码,顾名思义,它是一个委派密码编码器
,它将具体编码的实现根据要求委派给不同的算法,以此来实现不同编码算法之间的兼容和变化协调.
可以简单地理解为,遇到新密码,DelegatingPasswordEncoder
会委托给BCryptPasswordEncoder(encodingId为bcryp*)
进行加密,同时,对历史上使用ldap,MD4,MD5等等加密算法的密码认证保持兼容(如果数据库里的密码使用的是MD5算法,那使用matches方法认证仍可以通过,但新密码会使bcrypt进行储存
),十分神奇
BCryptPasswordEncoder
spring security中的BCryptPasswordEncoder方法采用SHA-256+随机盐+密钥对密码
进行加密。
因为有随机盐的存在,所以相同的明文密码经过加密后的密码
是不一样的,盐在加密的密码中是有记录的,所以需要对比的时候,springSecurity是可以从中获取到盐的
SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆
的。
部分知识引用自:
https://blog.csdn.net/alinyua/article/details/80219500
springSecurity之PasswordEncoder相关推荐
- SpringSecurity-12-PasswordEncoder密码加密简介
SpringSecurity-12-PasswordEncoder密码加密简介 为什么密码加密? 国内的每一个开发社区在2011年发生过被黑客攻击,盗取用户信息,600多万的明文密码信息被盗取,大量用 ...
- Spring Security身份认证Authentication
文章目录 Authentication(身份认证框架)的架构 SecurityContextHolder AuthenticationManager AbstractAuthenticationPro ...
- [SpringSecurity]基本原理_两个重要的接口_UserDetailsService接口和PasswordEncoder接口
UserDetailsService接口 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的.而在实际项目中 账号和密码都是从数据库中查询出来的. 所以我们要通过自定义 ...
- SpringBoot+SpringSecurity前后端分离+Jwt的权限认证(改造记录)
欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/zzzgd_666/article/details/96444829 前言 一般来说,我们用Spr ...
- Spring Security 3.1 中功能强大的加密工具 PasswordEncoder
去年发生的密码泄漏事件,我们也对密码加密做了重新研究. 在筛选加密方法的过程中,发现了Spring Security 3.1.0版本中提供了新的PasswordEncoder,它的加密方法非常给力! ...
- 【Spring框架家族】Spring--Security权限控制密码加密
Spring Security简介 Spring Security是 Spring提供的安全认证服务的框架. 使用Spring Security可以帮助我们来简化认证 和授权的过程.官网:https: ...
- SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制
文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...
- Spring-Security 简介、入门案例详解、安全框架、权限验证 SSM项目 使用 JavaConfig配置
Spring-Security 简介 一.介绍 二.详细步骤 1.创建一个maven项目 添加web 框架. 2.导入依赖 3.项目整体结构 4.Spring 容器配置 5.servletContex ...
- 手把手带你撸一把springsecurity框架源码中的认证流程
提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...
最新文章
- Python之日志处理(logging模块)
- signature=d3634edefd0f91592d1c7b65bef4a31d,Additional file 14
- (3) Hibernate的查询 标准(Criteria)查询
- 面向对象的三大特性————继承,多态
- [CF 526 F] Pudding Monsters(单调栈 + 线段树)
- 配置linux系统自带apache+php+mysql
- SaaS 不懂留存!别玩
- 判断是否是2的N次方各方法运行速度比较
- 如何快速掌握man手册的使用
- C语言基础进阶之 MessageBox()用法简介
- 视觉十四讲第六章G2O实践出错后的解决方法
- oracle 12 去掉 cdb,Oracle 12c CDB 和PDB 数据库的启动与关闭 说明
- 首次参加齐鲁软件设计大赛经验(及总结出的划水要点)
- 我常去逛的iOS干货文章、blog等【持续更新】 --转
- Markdown文档数学公式的使用
- [植树造林小游戏1.1]
- 让div中的p标签文字垂直居中的方法
- FFmpeg滤镜:制作图片视频流(续)
- 学习随笔---数据库管理系统DBMS
- 高精度的商业电子邮件入侵检测
热门文章
- 简洁好用的3个研发项目管理工具
- 路径规划算法C++实现(三)--DWA
- Linux rsync命令用法
- 机器学习——支持向量机(SVM)之超平面、间隔与支持向量
- CSharp-基础-集合的使用
- ReleaseCapture 以及 SetCapture 函数 及其应用
- 分析器错误消息: 无法执行程序。所执行的命令为 C:\Windows\Microsoft.NET\Framework\v4.0 \csc.exe/noconfig/fullpaths@C:\W
- 人脸识别之人脸对齐(一)--定义及作用
- 笔记:期权定价-无套利理论
- Oracle数据库中的级联查询、级联删除、级联更新操作教程