1、什么是彩虹攻击
彩虹攻击是通过使用预先计算的彩虹表来快速破解哈希值,从而破解你的密码。 比如攻击者通过一些常规密码算法如bcrypt,crypto等得到下面一张表:
| 密码 | 加密后密码 |
|---|---|
| admin | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
| admin123 | eyJzdWIiOjEsImlhdCI6MTc0NzcxMjA3Niwi |
| 123456 | ZXhwIjoxNzQ3NzEyMTM2fQ.dFgp5N32ILmuT |
攻击者使用程序用彩虹表中的每个秘密去测试,一旦找到一个一致的,攻击者就会拿到密码。
1.1、彩虹攻击的工作原理
- 预计算彩虹表:攻击者选择一个目标哈希函数,并预先计算出一系列可能的输入及其哈希值,存储在彩虹表中。
- 获取哈希值:通过某种手段获得一个或多个哈希值,通常是用户密码的哈希。
- 查找彩虹表:使用彩虹表来查找与已知哈希值相对应的原始输入(通常是密码)。
- 逆向链条:一旦找到匹配的链条,逆向跟踪这条链,重现原始输入。
1.2、防御措施
- 使用强哈希函数:如SHA-256或更高版本的哈希算法,生成更长的哈希值,增加彩虹表预计算的难度。
- 盐值:在哈希函数中添加随机数据(盐值),使得相同的密码产生不同的哈希值,增加破解难度。
- 密码策略:实施复杂的密码策略,限制密码的长度和复杂性,提高安全性。
我们使用盐值的方式,在密码加密后再使用盐值进行一次hash算法,这样计算出的密码与常规密码算法计算出的密码就差异很大,攻击者就很难用彩虹表进行攻击了。 argon2加密工具自动对加密后密码进行hash,同一个密码每次得到的加密结果会不一样,避免我们自己手工写代码进行hash,下面就用argon2实现密码加密和验证功能。
2、argon2加密
2.1、安装依赖
··· pnpm i argon2 ···
2.2、修改注册代码
arth.service.ts中增加注册功能:
/*
* 注册
* @param createUserDto
* @returns
*/
async signUp(createUserDto: CreateUserDto) {
const user=await this.userService.findOneByName(createUserDto.username);
if(user){
throw new ConflictException('用户已经存在');
}
//使用argon2对密码进行加密处理
const { password }=createUserDto;
const hashPassword=await argon2.hash(password);
createUserDto={...createUserDto,password:hashPassword};
return this.userService.create(createUserDto);
}
在auth.controller.ts中对应的接口代码:
@Post("signup")
@AllowNoToken()
registerUser(@Body() createUserDto: CreateUserDto){
return this.authService.signUp(createUserDto)
}
测试:
如果只换用户名,密码与上次一致,得到的密码是不一致的。
注意:用户实体中不能有salt字段,因为argon2是自动处理salt值的,如果有这个字段系统会提示salt没有默认值错误。
2.3、修改登录时密码校验逻辑
修改auth.service.ts中的validateUser代码,注意,我们不是在用户登录时验证的密码,而是在本地策略中验证的,本地策略调用的是validateUser方法,所以修改该方法。
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.userService.findOneByName(username);
if(!user) throw new UnauthorizedException("用户不存在");
//使用argon2进行密码验证
const isPasswordMatch =await argon2.verify(user.password,pass);
if(!isPasswordMatch) throw new UnauthorizedException("密码不正确");
return {id:user.id,email:user.email,name:user.username};
}
测试: