九、防范彩虹攻击(nestjs+next.js从零开始一步一步创建通用后台管理系统)

256 阅读3分钟

1、什么是彩虹攻击

彩虹攻击是通过使用预先计算的彩虹表来快速破解哈希值,从而破解你的密码。 比如攻击者通过一些常规密码算法如bcrypt,crypto等得到下面一张表:

密码加密后密码
admineyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
admin123eyJzdWIiOjEsImlhdCI6MTc0NzcxMjA3Niwi
123456ZXhwIjoxNzQ3NzEyMTM2fQ.dFgp5N32ILmuT

攻击者使用程序用彩虹表中的每个秘密去测试,一旦找到一个一致的,攻击者就会拿到密码。

1.1、彩虹攻击的工作原理

  1. 预计算彩虹表‌:攻击者选择一个目标哈希函数,并预先计算出一系列可能的输入及其哈希值,存储在彩虹表中。
  2. 获取哈希值‌:通过某种手段获得一个或多个哈希值,通常是用户密码的哈希。
  3. 查找彩虹表‌:使用彩虹表来查找与已知哈希值相对应的原始输入(通常是密码)。
  4. 逆向链条‌:一旦找到匹配的链条,逆向跟踪这条链,重现原始输入‌。

1.2、防御措施

  1. 使用强哈希函数‌:如SHA-256或更高版本的哈希算法,生成更长的哈希值,增加彩虹表预计算的难度。
  2. 盐值‌:在哈希函数中添加随机数据(盐值),使得相同的密码产生不同的哈希值,增加破解难度。
  3. 密码策略‌:实施复杂的密码策略,限制密码的长度和复杂性,提高安全性‌。

我们使用盐值的方式,在密码加密后再使用盐值进行一次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)
    }

测试:

image.png

如果只换用户名,密码与上次一致,得到的密码是不一致的。 image.png

注意:用户实体中不能有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};
  }

测试:

image.png

image.png