美文网首页个人学习
Java 实现 BCrypt

Java 实现 BCrypt

作者: 又语 | 来源:发表于2020-04-12 17:24 被阅读0次

本文介绍 Java 语言实现 BCrypt Hash 的方法。


目录

  • BCrypt 简介
    • 版本历史
    • 散列值解析
  • 代码实现
    • BCrypt
    • BCryptPasswordEncoder

BCrypt 简介

BCrypt 是由 Niels Provos 和 David Mazières 基于 Blowfish 加密算法设计的密码散列函数。BCrypt 引入加盐的机制以抵御彩虹表攻击。BCrypt 是一种自适应函数,可以通过增加迭代次数使得计算变慢,因此即使算力提升,也可以通过此方法抵御暴力破解。

版本历史
  • $2$
    1999 年最原始的 BCrypt 规范定义的前缀是 $2$,这是遵循了 OpenBSD 密码文件中存储密码时使用的模块化加密格式格式:
    • $1$: MD5-based crypt ('md5crypt')
    • $2$: Blowfish-based crypt ('bcrypt')
    • $sha1$: SHA-1-based crypt ('sha1crypt')
    • $5$: SHA-256-based crypt ('sha256crypt')
    • $6$: SHA-512-based crypt ('sha512crypt')
  • $2a$
    最原始的规范中并未定义如何处理非 ASCII 码的字符以及如何处理 null 终止符。针对这些情况规范进行了修改(进行此更改后,版本更改为 $2a$):
    • 字符串必须为 UTF-8 编码
    • 必须包含空终止符
  • $2x$, $2y$
    2011 年 6 月,在 BCrypt 的 PHP 实现 crypt_blowfish 中发现了一个 Bug,此版本标记更改仅限于 crypt_blowfish
  • $2b$
    2014 年 2 月在 OpenBSD 实现中发现了一个 Bug,将字符串的长度存储在一个无符号字符(即 8 位字节)中。如果密码长度超过 255 个字符,它将溢出并以 255 换行。BCrypt 是为 OpenBSD 创建的。 当他们的库中有错误时,他们决定更改版本号。
散列值解析
  1. BCrypt 计算出的散列值以 $2a$2b$2y 开头,表明此 HASH 字符串是一个模块化加密格式的 BCrypt 散列值,如:
$2a$10$5JoiLeDDev5v8BcQLoJNf.yKjrs.WpM57Vd7/qs3oBMYLRnE34Kki
$2b$10$vHDNqGQB09PeE7FHTbiNkuzHHJudkvbTrpq8RdLKj7Ugg59dET/4y
$2y$10$Y3MQpVSieTcPDc1BfLLuseGHlBjm93luly/Bu.X6hUyzqplNko2wW
  1. 散列值的第二部分指明了一个消耗参数,此参数将密钥扩展迭代计数指定为 2 的幂,如上例中的 $10,指明密钥扩展迭代次数是 210 次幂。

代码实现

首先,添加 spring-security-core 依赖,commons-lang3 依赖主要为了在单元测试中引用 RandomUtils 类。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.3.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
    <scope>test</scope>
</dependency>
BCrypt

通过 org.springframework.security.crypto.bcrypt.BCrypt 实现。

package tutorial.java.util;

import org.apache.commons.lang3.RandomUtils;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCrypt;

public class BCryptTest {

    @Test
    public void testBCrypt() {
        String salt = BCrypt.gensalt(RandomUtils.nextInt(10, 12));
        String password = "BCrypt Test" + RandomUtils.nextInt(0, 999);
        for (int i = 0, length = RandomUtils.nextInt(1, 10); i < length; i++) {
            String hash = BCrypt.hashpw(password, salt);
            System.out.println(hash);
            BCrypt.checkpw(password, hash);
        }
    }
}

运行结果:

$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6

注意:每次计算得到的散列值相同。

BCryptPasswordEncoder

通过 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder 实现。

package tutorial.java.util;

import org.apache.commons.lang3.RandomUtils;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptTest {

    @Test
    public void test() {
        String password = "BCrypt Test" + RandomUtils.nextInt(0, 999);
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
            String encodeResult = bCryptPasswordEncoder.encode(password);
            System.out.println(encodeResult);
            Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
        }
        bCryptPasswordEncoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B);
        for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
            String encodeResult = bCryptPasswordEncoder.encode(password);
            System.out.println(encodeResult);
            Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
        }
        bCryptPasswordEncoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2Y);
        for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
            String encodeResult = bCryptPasswordEncoder.encode(password);
            System.out.println(encodeResult);
            Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
        }
    }
}

运行结果:

$2a$10$KGQsOj4V1rmU/ydyYF8.i.5mhk7sn14sUzSQCIu6tD1Z1cLSjDeqW
$2a$10$Xq219cgK3HAQLt6l95PL7.V17x5edBobdh/3X6OZUR7ar2yafMrpy
$2b$10$s60GvG4hjdxbY5IyVoqlHO7KRT4kLzZUGzYYIjTtad4e7OYEWb9Xm
$2b$10$WQEeE.W19F3IyCWOi6R2.OxITO.ScokZO2XXuw9y5ek1Ac8Wu2JO2
$2b$10$A5je.3QPyDf2j3eHNoeP.OT.t.d/iMrkUm2tJaTnFCNBQJNQonKRu
$2b$10$ZU.vIkomWOAgHGYu.RjkDOqeZslrRjwt8aeW49ILux2Bwxlawjw/G
$2b$10$DjAMEv4naapKQI8K02R96OAD.DkEa5heiA3BKABguPm7VkjTJbkFa
$2y$10$Ij9o8DdlZDw6N8PsVms1nuUc0YawE3bYeskvRiGPIAs4ZueWQ2MyO
$2y$10$n4bpgFBgRkgu5QNzx5Tur.B4jliy2O4dE2zy49oAD4522f37PPOAK

注意:每次计算得到的散列值不同。

相关文章

网友评论

    本文标题:Java 实现 BCrypt

    本文链接:https://www.haomeiwen.com/subject/dlhbmhtx.html