KDF
KDF 密钥派生函数
在现实生活中,我们更倾向于使用密码来保护自己的数据,而不是二进制的密钥。因为相比于二进制复杂的密钥,字符形式(小写字母,大写字母,数字,特殊符号等的组合)才符合人类正常的思维。
可对计算机来说这相反,现代密码学的很多算法都要求输入是一个大的数字,二进制的密钥就是这样一个大的数字。 因此显然我们需要一个将字符密码(Password)转换成密钥(Key)的函数,这就是密钥派生函数 Key Derivation Function。
直接使用SHA-一类的哈希函数加密password是不可取的,因为password通常都包含着个人的情感元素,这是容易记忆但容易受到猜测的,通常密码不会很长,在10位左右。另外,有的人为了方便,还会选择一些常见的弱密码,比如123456,admin,个人生日等。这就导致如果直接使用SHA-类的算法,许多密码将很容易被暴力破解,字典攻击,彩虹表攻击等手段猜测出来。
KDF 目前主要从如下三个维度提升 hash 碰撞难度:
- 时间复杂度:对应 CPU/GPU 计算资源
- 空间复杂度:对应 Memory 内存资源
- 并行维度:使用无法分解的算法,锁定只允许单线程运算
主要手段是加盐,以及多次迭代。
因为相比其他加密哈希算法,KDF 具有一个独特属性——计算速度很慢,而且从设计上就使其计算速度难以提升,所以 KDF 也被称作「慢哈希算法」。
目前比较著名的 KDF 算法主要有如下几个:
- PBKDF2:这是一个非常简单的加密 KDF 算法,目前已经不推荐使用。
- Bcrypt:安全性在下降,用得越来越少了。不建议使用。
- Scrypt:可以灵活地设定使用的内存大小,在 argon2 不可用时,可使用它。
- Argon2:目前最强的密码 Hash 算法,在 2015 年赢得了密码 Hash 竞赛。
Scrypt
Scrypt 是一个强大的密钥派生函数,其通过内存密集的计算方式来抵抗 GPU、ASIC、FPGA 这类密码破解硬件的攻击。
Scrypt 接收多个输入参数,进行计算后输出密钥:
1 | key = Scrypt(password, salt, N, r, p, derived-key-len) |
其中的参数被称为" Scrypt 配置参数",说明如下:
N
- 迭代次数,将影响 CPU 和内存用量,例:16384 、2048 ;r
- 块大小,将影响 CPU 和内存用量,例:8 ;p
- 并行因数 (并行运行的线程数,将影响 CPU 和内存用量),通常为 1 ;password
- 输入的密码(推荐至少为 8 - 10 个字符);salt
- 安全产生的随机字节序列(最小为 64 位,推荐 128 位);derived-key-len
- 输出的密钥要有多少字节长,例如 32 (256 位)
Scrypt 计算过程中的每一步都会 按照强相关的顺序 访问内存,这就让内存读写性能成为了算法速度的瓶颈。
具体怎么选择参数,要取决于我们能够等待的时间和所需的安全等级(即抗破解的能力):
- 用于交互式登录的示例参数:N=16384, r=8, p=1(RAM = 16MB)。交互式的登录一般耗时都要小于 0.5s ,所以必须快速完成计算。同样的,对于服务端而言,如果同时有很多用户登录,那么 Scrypt 的缓慢会拖慢整个系统;
- 用于文件加密的示例参数:N=1048576, r=8, p=1(RAM = 1GB)。当要加密硬盘时,通常不会频繁解密数据(一天可能只解密 2 ~ 3次),所以你可能会愿意多等 2 ~ 3 秒作为提升安全性的代价。
在 MyEtherWallet 加密钱包应用中,默认的参数是 N=8192, r=8, p=1 。对于此类应用而言,该强度不够高,但可以通过要求用户输入又长又复杂的密码来对抗密码破解攻击。
1 | import os |
在配置合适的前提下,Scrypt 被认为是高度安全的 KDF 函数,所以可以用在任何需要 KDF 的地方——加密钱包、文件、App 密码等场景都可以
Bcrypt
Bcrypt 也是一个 KDF ,问世时间早于 Scrypt ,对于 ASIC 、GPU 攻击的抗性相对弱一些。其虽然也可以配置迭代数,但由于对内存的压力较小,因此比较容易构建相应的硬件加速密码破解器。
在很多的应用、框架和工具中(比如 WordPress 站点的数据库),Bcrypt 加密后的密码都是和算法设置以及盐保存在一起的,体现为一个单一的字符串(字符串有着特定的格式)。这个字符串包含数个部分,以 $
符号分割
例如:
1 | $2a$07$wHirdrK4OLB0vk9r3fiseeYjQaCZ0bIeKY9qLsNep/I2nZAXbOb7m |