BCrypt加密算法的使用及原理

系列文章目录

1.SpringBoot整合RabbitMQ并实现消息发送与接收
2. 解析JSON格式参数 & 修改对象的key
3. VUE整合Echarts实现简单的数据可视化
4. List<HashMap<String,String>>实现自定义字符串排序(key排序、Value排序)
5. 使用JAVA代码实现生成二维码

更多文章可看我主页哦~



前言

    在我们开发过程中肯定会对于一些保密数据进行加密存储,加密的方式有很多,例如大家常见的MD5、SHA-256等加密方法。这边我以前使用的是MD5,主要是因为MD5容易被解密。因为MD5在值相同时,加密出的内容都是相同的。这样对于数据很容易就会被破解,怎样能做到相同值在加密后的值不相同呢?
    引入我们今天的主角BCrypt算法,BCrypt算法是一种用于密码散列的加密算法,设计用于安全地处理用户密码。它结合了散列算法和盐的使用,具有较高的安全性抗破解能力
    下面带大家了解BCrypt算法具体的使用方法以及一些原理的解析,让大家能够清晰的明白该算法的用途以及解决大家心中的疑惑~


一、加密原理及代码

1.1 原理

    相信大家在看这篇文章时也是见过BCrypt算法加密后的字符串样子,但对于里面的内容以及为什么会生成这样的字符串还不太明白,先来看下图:
在这里插入图片描述
上图中就是BCrypt算法生成字符串的结构组成,由四个部分组成:

  • 算法标识符 (2a):这个前缀表示使用了Bcrypt算法。 2 a 2a 2a是Bcrypt的一个版本标识符。
  • 代价因子 (10):代价因子表示算法的复杂度,也就是加密过程中迭代的次数。10表示算法使用2的10次方,即1024轮处理。这决定了计算哈希所需的时间和资源量。
  • 盐(Salt) :盐是一个16字节(128位)的随机值,用于增加哈希的唯一性和安全性。它经过Base64编码后变成22个字符的字符串。
  • 哈希值 (Hash):这是经过Bcrypt算法处理后的哈希值。它是24字节(192位)的原始哈希值,经过Base64编码后变成31个字符的字符串。

1.2 代码

    下面我们来使用代码生成字符串,因为相同值生成的密文是不同的,所以这里代码也做比较。是具体代码如下:

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class test {
    // 创建 BCryptPasswordEncoder 实例
    private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    /**
     * 加密密码
     *
     * @param rawPassword 明文密码
     * @return 加密后的密码
     */
    public static String encryptPassword(String rawPassword) {
        return passwordEncoder.encode(rawPassword);
    }

    public static void main(String[] args) {
        String password = "123456";
        String encryptedPassword = encryptPassword(password);
        String encryptedPassword1 = encryptPassword(password);
        // 比对加密后的密码
        boolean isMatch = passwordEncoder.matches(password, encryptedPassword);
        boolean isMatch1 = passwordEncoder.matches(password, encryptedPassword1);

        System.out.println("Encrypted Password: " + encryptedPassword);
        System.out.println("Encrypted Password1: " + encryptedPassword1);
        System.out.println("Passwords match: " + isMatch);
        System.out.println("Passwords match1: " + isMatch1);
    }
}

注:导入包security权限包,需要引入依赖。

1.3 运行效果

    从效果图可以看到,同样是进行原密码:123456的加密,加密的结果是不一样的。但是在通过原密码和加密后的密码对于时,都可以返回true。表名都是123456密码加密的。如下图所示:
在这里插入图片描述
这里大家可能会有2个疑问

  1. 为什么相同参数在加密时会生成不同的密文?
  2. 密文都不同了,怎么去和原密码进行的比较能得出结果呢?(即2个不同的密文,都和原文比较得出相同的结果)

二、原理

2.1 密文随机生成

  • 盐的使用:BCrypt 在加密过程中会生成一个独特的盐,并将其与密码进行混合。每次加密时,盐都会不同,因此即使原始密码相同,每次加密生成的加密值也会不同。

  • 加密过程:BCrypt 的加密过程包括盐的生成和散列计算。由于盐是随机的,生成的散列值会有所不同。即使输入相同,但由于每次使用不同的盐,输出也会不同。

    上述加密字符串的结构是由四部分组成的,其中包含盐。这里BCrypt自动生成一个随机盐值,盐的目的是防止相同密码生成相同的散列值。也正因为如此,hash值也会发现不一样。所以我们看到的加密字符串的内容是不一样的。

2.2 passwordEncoder.matches 方法比较结果为什么都为true?

注:passwordEncoder.matches 方法在比较密码时并不是直接比较加密后的值,而是执行以下步骤:

  • 提取盐:从加密后的值中提取盐。
  • 重新加密:使用提取出的盐和提供的密码重新计算加密值。
  • 比较:将重新计算的加密值与存储的加密值进行比较。

因为提取的是加密字符串中的盐,所以在传入的原密码使用提取的盐进行加密,得到的密文肯定是和一致的相同的盐散列值(hash)一样。所以会返回true,

大家在这里应该会比较清楚的知道输出效果图中的含义了。但是我来问大家一个问题:它是怎么提取加密后值中的盐呢?

2.3 如何提取加密后值中的盐

    这个其实第一张图认识字符串的组成结构时介绍了生成的规则。那么生成的规则也将是用来提取盐的重要部分:

  1. 首先底层会将加密的值进行拆分成四部分。
  2. 获取到盐的22个字符的字符串
  3. 因为这个字符串是BASE64加密后的,我们解码后也可以拿到一个16字节(128位)的随机值。

2.4 提取盐之后,如何进行的比较

  1. 使用提取出的盐原密码生成新的哈希值。此时原密码是必须的,因为新的哈希值是基于原密码和盐计算出来的。
  2. 将生成的哈希值与存储的哈希值进行比较。这一步确保了输入密码的正确性。

注:这里是重点,matches 方法比较原密码和加密是否一直时,是通过哈希值(也就是散列值)进行的比较。因为算法标识符 (2a)、代价因子(10)是固定的,盐的话也是通过密文去生成的,所以肯定一致。可能出现不一样的地方只有哈希值(因为哈希值的生成是通过提取出的盐和原密码生成的)

三、如何运用

    大家到这里应该对BCrypt算法有了一定的理解,但光了解原理是不够的。先说一下场景:我们以常见的密码存储为例:
数据库中的密码存储是相关重要的,都会使用加密算法实现。所以这里我们也可以运用BCrypt算法。

  1. 首先用户表密码字段需存入BCrypt算法加密的密文(这里在用户注册时,可通过下述代码生成密文)。
public static String encryptPassword(String rawPassword) {
        return passwordEncoder.encode(rawPassword);
    }
  1. 在用户登录时,通过用户名查询表,获取对应的实体。然后拿到实体中的密码作为matches(password, encryptedPassword);方法中的encryptedPassword参数,password就是用户输入的参数值。
  2. 调用matches方法查看返回值,如果返回true,则用户名和密码正确。否则返回“用户名或密码错误”。

这里简写一个实现代码,来体现步骤二、三。具体如下所示:

@PostMapping("/login")
    public R<User> login( @RequestBody User user){
      
        //1.将页面提交的密码
        String password=user.getPassword();

        //2.根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,user.getUsername());
        User selectuser = userService.getOne(queryWrapper);
        // 比对加密后的密码
        boolean isMatch = passwordEncoder.matches(password, selectuser.getPassword());
        if(!isMatch){
            return R.error("用户名或密码不正确");
        }
        
        //封装token
        Map<String,Object> map=new HashMap<>();
//        map.put("token",token);

        return R.success(newUser,map);
    }

当然,这只是一个常规的场景需要用到对数据的加密。正常情况下对于表中的关键数据大多数都应该进行加密处理,例如用户的身份证号等重要信息值。这样即使是数据库数据泄露,也不会造成太大的影响,加密的数据很安全。

四、为什么要用BCrypt算法进行加密

    Bcrypt算法是一个密码哈希算法,它的好处有很多,专门设计用于保护密码存储,其主要优点包括:

  • 抗暴力破解:Bcrypt使用自适应的计算复杂度(代价因子),使得随着计算能力的提升,算法仍然能够保持其破解难度。这种自适应性增加了暴力破解的难度。
  • 内置盐:Bcrypt自动生成盐,并将其与哈希值一起存储。盐的使用防止了相同密码的哈希值重复,增强了安全性。
  • 抗彩虹表攻击:由于每个密码都包含独特的盐,即使两个用户使用相同的密码,哈希值也会不同。这使得预计算的彩虹表攻击变得不切实际。
  • 自适应性:代价因子(工作因子)可以调整,允许增加计算复杂度以抵御不断提高的计算能力。这使得Bcrypt能够应对未来的硬件进步。
  • 稳定性和可靠性:Bcrypt算法经过广泛使用和测试,被认为是一个成熟、可靠的密码哈希算法。
  • 防止硬件加速攻击:Bcrypt的设计旨在避免硬件加速攻击(例如GPU、FPGA等),因为它需要大量的计算资源,而不是简单的并行处理。

总结

    相信大家通读完全文后也对于BCrypt算法有了一定的理解。希望大家在日后的学习或者工作中能够运用到,对于数据的安全性是有很大帮助的。
    我在自己学习时也是遇到了很多的问题,本想着加密算法,没打算写文章的。但是在网上搜索时发现了很多文章介绍的不够全面,有些细节点在读完之后还是很疑惑。导致我在学习的过程中耗费了不少的时间成本。为了让大家的学习成本降低,所以写了这篇文章。希望大家能够通过这篇文章去理解和会去使用BCrypt加密算法~
    如果有什么问题都可以留言或者私信我哦~ 也希望大家有更多见解的朋友能够下方留言,让更多的人对BCrypt算法的知识点体会更加深刻~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心态还需努力呀

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值