使用 PBKDF2 加密,告别明文密码,让拖库、撞库、洗库、脱库见鬼去吧!

前段时间,网上爆出了 12306 的 60 万账号在网上出售。归根结底是因为有人使用了存储明文密码。作为一个程序员,对用户口令加密是一个最基本的简单功能,然而,越简单的东西,越容易被忽视。今天我就给大家分享一个 PBKDF2 加密,让拖库、撞库、洗库、脱库见鬼去吧!(关于拖库、撞库、洗库、脱库可以看我的这篇文章《12306 甩锅?拖库、洗库、撞库、脱库来了解一下!》)

 

PBKDF2 全称是 Password-Based Key Derivation Function,2 呢?就是代表第二个版本。PBKDF2 是一个用来导出密钥的函数,常用于生成加密的密码。它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。

我们都知道,密码加密,需要使用哈希算法进行加密。包括大家熟知的,md5 也是一种 Hash 算法。

因为哈希算法是单向的,可以将任何大小的数据转化为定长的“指纹”,而且无法被反向计算。即使数据源只改动了一丁点,哈希的结果也会完全不同。

这样的特性使得 Hash 算法非常适合用于保存密码,因为我们需要加密后的密码无法被解密,同时也能保证正确校验每个用户的密码。但是哈希加密可以通过字典攻击和暴力攻击破解。

密码加盐,又称盐值加密(关于盐值加密可以看我的这两篇文章《java 实现MD5 盐值加密功能》、《为什么要在MD5加密的密码中加“盐”》)。盐是一个添加到用户的密码哈希过程中的一段随机序列。这个机制能够防止通过预先计算结果的彩虹表破解。每个用户都有自己的盐,这样的结果就是即使用户的密码相同,通过加盐后哈希值也将不同。为了校验密码是否正确,我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中,或者直接存为哈希字符串的一部分。

网上目前有一个关于 PBKDF2 的实现库,https://github.com/defuse/password-hashing,今天我们就用它来实现明文密码的加密。

有人可能会问,你为什么不自己实现一个 Hash 算法来加密啊?这是个好问题。首先,我要告诉你的是,千万别自己去实现一个 Hash 去加密你的密码。类似的这些加密方式都不建议使用,md5(sha1(password))、md5(md5(salt) + md5(password))、sha1(sha1(password))、sha1(str_rot13(password + salt))、md5(sha1(md5(md5(password) + sha1(password)) + md5(password))))。为什么呢?因为别人既然能获取到你的数据库,代码可能也就能获取到,一旦你的源码被获取,密码就等于明文了。所以千万不要自己去造轮子,使用可靠的轮子更好!

PBKDF2 函数的定义

DK = PBKDF2(PRF, Password, Salt, c, dkLen)
PRF是一个伪随机函数,例如HASH_HMAC函数,它会输出长度为hLen的结果。
Password是用来生成密钥的原文密码。
Salt是一个加密用的盐值。
c是进行重复计算的次数。
dkLen是期望得到的密钥的长度。
DK是最后产生的密钥。

PBKDF2 的算法流程

DK 的值由一个以上的 block 拼接而成。block 的数量是 dkLen/hLen 的值。就是说如果 PRF 输出的结果比期望得到的密钥长度要短,则要通过拼接多个结果以满足密钥的长度。

PBKDF2 算法流程

password-hashing 提供了非常多的语言支持,大家常见的 Java、pHP、C#、Ruby 等都有。源码可以在我上面发的 github 仓库中查看。

password-hashing 也提供了对应语言的使用方法。下面看一个简单的例子:

public static void truncatedHashTest() {
    String userString = "password!";
    String goodHash = "";
    String badHash = "";
    int badHashLength = 0;
    try {
        goodHash = PasswordStorage.createHash(userString);
    } catch (Exception e) {
        System.out.println(e.getMessage());
        System.exit(1);
    }
    badHashLength = goodHash.length();
    do {
        badHashLength -= 1;
        badHash = goodHash.substring(0, badHashLength);
        boolean raised = false;
        try {
            PasswordStorage.verifyPassword(userString, badHash);
        } catch (PasswordStorage.InvalidHashException ex) {
            raised = true;
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
            System.exit(1);
        }
        if (!raised) {
            System.out.println("Truncated hash test: FAIL " +
                "(At hash length of " +
                badHashLength + ")"
                );
            System.exit(1);
        }
    // The loop goes on until it is two characters away from the last : it
    // finds. This is because the PBKDF2 function requires a hash that's at
    // least 2 characters long.
    } while (badHash.charAt(badHashLength - 3) != ':');
    System.out.println("Truncated hash test: pass");
}

计算的过程,就和 PBKDF2 的算法流程一致。更多用法,大家去参考官网即可。当然也可以使用谷歌的 Tink 框架(参考我的这篇文章《谷歌 Tink(加密API) 教程》)。

使用 PBKDF2 加密,告别明文密码,让拖库、撞库、洗库、脱库见鬼去吧!

: » 使用 PBKDF2 加密,告别明文密码,让拖库、撞库、洗库、脱库见鬼去吧!

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/java/252006.html

(0)
上一篇 2022年5月3日 21:59
下一篇 2022年5月3日 22:03

相关推荐

发表回复

登录后才能评论