前段时间,网上爆出了 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 输出的结果比期望得到的密钥长度要短,则要通过拼接多个结果以满足密钥的长度。
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 加密,告别明文密码,让拖库、撞库、洗库、脱库见鬼去吧!
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/java/252006.html