密码存储演进史

自从互联网有了用户的那一刻起,存储用户密码这件事便成为了一个健全的系统不得不面对的一件事。

远古时期,明文存储密码可能还不被认为是一个很大的系统缺陷(事实上这是一件很恐怖的事)。提及明文存储密码,我立刻联想到的是 CSDN 社区在 2011 年末发生的 600 万用户密码泄露的事件,谁也不会想到这个和程序员密切相关的网站会犯如此低级的错误。

明文存储密码使得恶意用户可以通过 sql 注入等攻击方式来获取用户名和密码,虽然安全框架和良好的编码规范可以规避很多类似的攻击,但依旧避免不了系统管理员,DBA 有途径获取用户密码这一事实。事实上,不用明文存储存储密码,程序员们早在 n 多年前就已经达成了共识。

不能明文存储,一些 hash 算法便被广泛用做密码的编码器,对密码进行单向 hash 处理后存储数据库,当用户登录时,计算用户输入的密码的 hash 值,将两者进行比对。单向 hash 算法,顾名思义,它无法(或者用不能轻易更为合适)被反向解析还原出原密码。这杜绝了管理员直接获取密码的途径,可仅仅依赖于普通的 hash 算法(如 md5,sha256)是不合适的,他主要有 3 个特点:

  1. 同一密码生成的 hash 值一定相同
  2. 不同密码的生成的 hash 值可能相同(md5 的碰撞问题相比 sha256 还要严重)
  3. 计算速度快。

以上三点结合在一起,破解此类算法成了不是那么困难的一件事,尤其是第三点,会在下文中再次提到,多快才算非常快?按照相关资料的说法:

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相关推荐

  1. SpringSecurity-12-PasswordEncoder密码加密简介

    SpringSecurity-12-PasswordEncoder密码加密简介 为什么密码加密? 国内的每一个开发社区在2011年发生过被黑客攻击,盗取用户信息,600多万的明文密码信息被盗取,大量用 ...

  2. Spring Security身份认证Authentication

    文章目录 Authentication(身份认证框架)的架构 SecurityContextHolder AuthenticationManager AbstractAuthenticationPro ...

  3. [SpringSecurity]基本原理_两个重要的接口_UserDetailsService接口和PasswordEncoder接口

    UserDetailsService接口 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的.而在实际项目中 账号和密码都是从数据库中查询出来的. 所以我们要通过自定义 ...

  4. SpringBoot+SpringSecurity前后端分离+Jwt的权限认证(改造记录)

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/zzzgd_666/article/details/96444829 前言 一般来说,我们用Spr ...

  5. Spring Security 3.1 中功能强大的加密工具 PasswordEncoder

    去年发生的密码泄漏事件,我们也对密码加密做了重新研究.  在筛选加密方法的过程中,发现了Spring Security 3.1.0版本中提供了新的PasswordEncoder,它的加密方法非常给力! ...

  6. 【Spring框架家族】Spring--Security权限控制密码加密

    Spring Security简介 Spring Security是 Spring提供的安全认证服务的框架. 使用Spring Security可以帮助我们来简化认证 和授权的过程.官网:https: ...

  7. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  8. Spring-Security 简介、入门案例详解、安全框架、权限验证 SSM项目 使用 JavaConfig配置

    Spring-Security 简介 一.介绍 二.详细步骤 1.创建一个maven项目 添加web 框架. 2.导入依赖 3.项目整体结构 4.Spring 容器配置 5.servletContex ...

  9. 手把手带你撸一把springsecurity框架源码中的认证流程

    提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...

最新文章

  1. Python之日志处理(logging模块)
  2. signature=d3634edefd0f91592d1c7b65bef4a31d,Additional file 14
  3. (3) Hibernate的查询 标准(Criteria)查询
  4. 面向对象的三大特性————继承,多态
  5. [CF 526 F] Pudding Monsters(单调栈 + 线段树)
  6. 配置linux系统自带apache+php+mysql
  7. SaaS 不懂留存!别玩
  8. 判断是否是2的N次方各方法运行速度比较
  9. 如何快速掌握man手册的使用
  10. C语言基础进阶之 MessageBox()用法简介
  11. 视觉十四讲第六章G2O实践出错后的解决方法
  12. oracle 12 去掉 cdb,Oracle 12c CDB 和PDB 数据库的启动与关闭 说明
  13. 首次参加齐鲁软件设计大赛经验(及总结出的划水要点)
  14. 我常去逛的iOS干货文章、blog等【持续更新】 --转
  15. Markdown文档数学公式的使用
  16. [植树造林小游戏1.1]
  17. 让div中的p标签文字垂直居中的方法
  18. FFmpeg滤镜:制作图片视频流(续)
  19. 学习随笔---数据库管理系统DBMS
  20. 高精度的商业电子邮件入侵检测

热门文章

  1. 简洁好用的3个研发项目管理工具
  2. 路径规划算法C++实现(三)--DWA
  3. Linux rsync命令用法
  4. 机器学习——支持向量机(SVM)之超平面、间隔与支持向量
  5. CSharp-基础-集合的使用
  6. ReleaseCapture 以及 SetCapture 函数 及其应用
  7. 分析器错误消息: 无法执行程序。所执行的命令为 C:\Windows\Microsoft.NET\Framework\v4.0 \csc.exe/noconfig/fullpaths@C:\W
  8. 人脸识别之人脸对齐(一)--定义及作用
  9. 笔记:期权定价-无套利理论
  10. Oracle数据库中的级联查询、级联删除、级联更新操作教程