糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 了 密码该如何保存都不会?

了 密码该如何保存都不会?

时间:2021-06-19 14:47:39

相关推荐

了 密码该如何保存都不会?

大家好呀,我是小黑。

我们在开发应用时,只要涉及到用户,登录注册功能则是必不可少的。

但是,并不是所有人都能做好登录注册功能。比如最基本的密码应该如何保存?应该用哪种加密方式对密码进行加密都不是很清楚。

一旦出现数据库泄漏,密码外泄等问题,会对用户造成极大的损失。

密码该如何保存?

如果我们要在服务器中对用户进行身份验证,我们需要完成以下的步骤:

获取到要登录用户的用户名和密码;根据用户名在数据库中查找到用户;比较用户提供的密码和数据库中的密码是否一致。

那我们应该如何存储用户的密码呢?我们来看看都有哪些方式,以及存在的问题。

明文保存

将用户的密码以明文方式保存。

很显然,有点常识的人都应该知道,密码不能用明文保存的。但是话又说回来,系统都是由人开发的,开发系统的人可能并不专业。比如之前某个大型中文开发者社区,因为数据库泄露,导致大批用户的密码泄漏,而他们的密码就是明文保存的。

HASH保存

使用Hash函数计算出密码的hash值保存,可以解决密码直接暴露的问题。

Hash函数是一个单向函数,不能通过结果值反向得出原始值,Hash函数可以将一串密码转换成一个固定长度的字符串。

在用户注册时,将用户的密码使用Hash函数计算出Hash值后保存到数据库;当用户登录时,对用户提交的密码使用相同的Hash函数计算出Hash值,和数据库中的Hash值进行比较。

这样可以避免让攻击者直接获取到用户的密码明文,攻击者想通过暴力攻击将字符串计算出hash值则需要花费巨大的精力,并且Hash值越长破解难度越大。

但是通过彩虹表攻击,攻击者仍然可以成功破解。彩虹表是一个包含许多提前计算出Hash值的表,其中包含数百万个密码对应的hash值,对于一些简单密码可以非常快的破解。

所以,如果你不确定你注册的服务是采用哪种方式保存的密码,尽量将密码复杂度设置高一些。

加盐Hash

为了防止彩虹表攻击,可以使用Hash算法加盐处理。

是在进行Hash计算时,和原始密码拼接在一起进行计算的一个随机序列。

用户注册时,将密码和盐值组合后进行Hash计算,得到密码结果保存在数据库中;当用户在登录验证时,将原始密码加盐后进行Hash计算,得到结果值和数据库中的密码进行比较。

因为彩虹表中的密码和加盐后的密码不一样,可以防止彩虹表攻击。如果盐值足够长并且随机,那么就可以保证在彩虹表中不能找到和密码相同的hash值。

但是,由于攻击者是有可能获取到盐值的,攻击者可以调整彩虹表生成的算法,用获取到的盐值计算出新的彩虹表,同样可以获取到密码。虽然计算一个新的彩虹表花费的时间巨大,但是随着硬件条件越来越好,要计算出一张彩虹表会变得越来越容易。

所以,使用Hash算法加盐处理,可以保证密码不被快速破解,但是还不够安全。

密码加密函数

Hash函数设计的初衷并不仅仅是对密码进行Hash计算,所以Hash函数的运算速度非常快,但是这样一来,攻击者也能快速计算hash值,进行暴力破解。

为了解决这个问题,我们可以让Hash加密函数变慢

我们只要让密码加密的时间在用户能接受的时间内,尽量的慢,这样攻击者蛮力破解将会花费无限的时间。

有以下一些专门用来加密密码的算法:

bcryptscryptPBKDF2argon2

这些算法使用一些复杂的加密算法,并会故意让计算变慢。

工作因子

可以通过在算法中配置工作因子,来调整加密函数计算时间的缓慢程度。

每个密码加密算法都有自己的工作因子。工作因子影响密码编码的速度。例如,bcrypt有参数strength,该算法将使2的strength次方来计算哈希值。数字越大,编码越慢。

使用Spring Security加密密码

现在让我们看看 Spring Security 如何支持这些算法,以及我们如何使用它们加密密码。

PasswordEncoder

在Spring Security 中有一个PasswordEncoder接口。所有密码编码器都实现了该接口。

public interface PasswordEncoder {String encode(CharSequence rawPassword);boolean matches(CharSequence rawPassword, String encodedPassword);default boolean upgradeEncoding(String encodedPassword) {return false;}}

该接口中有两个方法:

encode()方法用户将明文密码转换为密文形式;

matches()方法用户将明文密码与密文密码进行比较。

BCryptPasswordEncoder

String plainPassword = "123456";// 工作因子int strength = 10;BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(strength, new SecureRandom());String encodedPassword = bCryptPasswordEncoder.encode(plainPassword);System.out.println(encodedPassword);

BCryptPasswordEncoder中的参数strength是密码加密算法的工作因子,Spring Security中的默认值为10

在创建时指定SecureRandom作为随机加盐生成器。

$2a$10$pYxXvggEgN7znYKofHIr/uRTw.dsYeW9mbxzNMSNOoGIYZU8twXNG

Pbkdf2PasswordEncoder

PBKDF2 算法不是为密码编码专门设计的,而是为了从密码中派生出密钥而设计的。当我们想用密码对某些数据进行加密时,通常需要密钥,但密码的强度不足以用作加密密钥。

String plainPassword = "123456";//加密秘钥String pepper = "小黑说JAVA";// 哈希次数int iterations = 200000;// 哈希长度int hashWidth = 256;Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder(pepper, iterations, hashWidth);pbkdf2PasswordEncoder.setEncodeHashAsBase64(true);String encodedPassword = pbkdf2PasswordEncoder.encode(plainPassword);System.out.println(encodedPassword);

Pbkdf2PasswordEncoder会多次在普通密码上运行哈希算法。我们可以定义输出的hash长度,并额外使用pepper让密码编码更安全。

WnCG4wMZFHPAD9DGg+SChNceQqbeAZRQyf2OHCK5WKdYBRzbeAGsQg==

Pbkdf2PasswordEncoder默认会执行185000哈希计算,默认的哈希长度为256。

SCryptPasswordEncoder

SCryptPasswordEncoder算法可以配置CPU和内存成本,通过这两项配置可以让攻击者破解密码的难度更大。

String plainPassword = "123456";// cpu消耗int cpuCost = (int) Math.pow(2, 14);// 内存消耗int memoryCost = 8;// currently not supported by Spring Securityint parallelization = 1;// 秘钥长度int keyLength = 32;// 盐值长度int saltLength = 64;SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder(cpuCost,memoryCost,parallelization,keyLength,saltLength);String encodedPassword = sCryptPasswordEncoder.encode(plainPassword);System.out.println(encodedPassword);

输出结果如下:

$e0801$PgZZvXdDjbxMZJi4eidFCHblUdvwOT/n0FZFyCWIHloqL6Wkbk7bAJ2nwVIWsW9PJTodncEtok1qcaWR+u+pZg==$lcqK7ACDTv8gG3ZwGoz0X7rn4EnZvnEcZ7rS0Qq31Ng=

Argon2PasswordEncoder

Argon2算法是 年密码哈希竞赛的获胜者。该算法也允许我们调整 CPU 和内存成本。该算法将所有参数保存在结果字符串中。

int saltLength = 16;int hashLength = 32;int parallelism = 1;int memory = 4096;int iterations = 3;Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder(saltLength,hashLength,parallelism,memory,iterations);String encodePassword = argon2PasswordEncoder.encode(plainPassword);

输出结果如下:

$argon2id$v=19$m=4096,t=3,p=1$uft4b+crs6tiwOhDnuFsIg$d/GXjYZnEw+/ubVnPqNeQDFX32GRYe+yTwuwydXLjos

在Spring Boot中设置PasswordEncoder

接下来,为了能更好的了解PasswordEncoder在Spring Boot中如何应用,我们先来开发一个Rest Api接口,并且配置Spring Security支持基于密码验证。

配置PasswordEncoder

首先,我们创建一个需要Spring Security保护的Rest API:

@RestControllerpublic class BlogRest {@GetMapping(path = "/blogs")public List<Blog> blogs() {return Lists.newArrayList(new Blog("hello world", "小黑说Java"));}}

我们需要/blogs接口的访问需要经过用户身份的验证。因此,我们使用 Spring Security 配置:

/*** @author 小黑说Java* @ClassName SecurityConfiguration* @Description* @date /2/3**/@Configuration@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.csrf().disable().authorizeRequests().antMatchers("/registration").permitAll().anyRequest().authenticated().and().httpBasic();}// other codes.}

该配置表示除了/registration外,其他的请求路径都需要进行身份验证;每当向应用程序发送 HTTP 请求时,Spring Security 都会检查Header是否包含Authorization: Basic <credentials>.如果未设置Header,则服务器会返回401;如果 Spring Security 找到对应Header,它将进行身份验证。

Spring Security 在进行身份验证时,需要从数据库中查询用户名、密码信息,需要提供一个UserDetailsService接口的实现类,实现该接口中的loadUserByUsername方法。所以我们定义如下接口DatabaseUserDetailsService:

/*** @author 小黑说Java* @ClassName DataBaseUserDetailService* @Description* @date /2/3**/@Service@Transactionalpublic class DataBaseUserDetailService implements UserDetailsService {private final UserDAO userDAO;private final UserMapper userMapper;public DataBaseUserDetailService(UserDAO userDAO, UserMapper userMapper) {this.userDAO = userDAO;this.userMapper = userMapper;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userDAO.selectByUserName(username);return userMapper.toUserDetails(user);}}

Spring Security中的AuthenticationProvider接口的实现在身份验证时将使用UserDetailsService来执行身份验证逻辑。

AuthenticationProvider接口的实现有很多,因为我们的用户信息存在数据库中,所以我们使用DaoAuthenticationProvider

@Configuration@EnableWebSecurityclass SecurityConfiguration extends WebSecurityConfigurerAdapter {private final DatabaseUserDetailsService databaseUserDetailsService;// constructor ...@Beanpublic AuthenticationProvider daoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();// 设置密码加密器provider.setPasswordEncoder(passwordEncoder2());// 设置用户信息查询服务provider.setUserDetailsService(this.databaseUserDetailsService);return provider;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// ...}

到这里,我们已经完成了Spring Security的配置,如果客户端发送带有基本身份验证的Header的HTTP请求,Spring Security将读取该Header信息,根据username获取数据库中的用户信息,并使用BCryptPasswordEncoder进行密码验证,如果一致则验证通过。如果不一致,服务器将响应 401。

用户注册服务

在验证用户身份之前,我们需要先在数据库中保存用户,也就是用户需要先注册账号。那么我们来实现一个用户注册的接口:

@RestControllerpublic class UserRest {private final UserRegistrationService userRegistrationService;public UserRest(UserRegistrationService userRegistrationService) {this.userRegistrationService = userRegistrationService;}@PostMapping("/registration")@ResponseStatus(code = HttpStatus.CREATED)public void register(@RequestBody UserDTO user) {// 注册用户userRegistrationService.register(user);}}

按照我们对Spring Security规则的定义,/registration路径的访问不需要进行身份验证。

我们在register方法中调用userRegistrationService.register(user)进行用户注册。

@Service@Transactionalpublic class UserRegistrationService {private final UserDAO userDAO;private final PasswordEncoder passwordEncoder;public UserRegistrationService(UserDAO userDAO, PasswordEncoder passwordEncoder) {this.userDAO = userDAO;this.passwordEncoder = passwordEncoder;}public void register(UserDTO userDTO) {User user = new User();user.setUserStatus(UserStatusEnum.INFORCE.getStatus());user.setUsername(userDTO.getUsername());user.setPassword(passwordEncoder.encode(userDTO.getPassword()));userDAO.insert(user);}}

在进行用户注册时,我们通过PasswordEncoder将用户提供的明文密码进行加密后,保存到数据库中。

小结

以上就是本期的主要内容,我们讲了密码应该如何在系统中保存,最没有常识和安全意识的就是明文保存;使用Hash算法保存会被彩虹表攻击,同样也不可取;而使用加盐Hash加密虽然能一定程度地降低彩虹表攻击的可能性,但是随着硬件性能的发展,同样可能被彩虹表攻击,所以我们应该选择一些特定的密码加密算法。比如

Bcrypt,Pbkdf2,Scrypt,Argon2等。

最后我们通过SpringBoot+Spring Security完成了一个用户登录和注册的功能。

希望本文能对你有所帮助,写文不易,需要一点正反馈,可以的话点个赞吧。

我是小黑,一名在互联网“苟且”的程序员

流水不争先,贵在滔滔不绝

如果觉得《了 密码该如何保存都不会?》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。