13.1 概述
密钥派生函数是一个函数它的功能是从一个密钥产生出一个或者多个密钥。
一些密钥派生函数会有一个(通常是可选的)salt参数。这个参数可以解决相同的输入密钥产生相同的输出密钥。在一些其他的密码系统,salts会和输入的密钥有本质的不同,salts通常不需要是私密的,并且可以重用。
密钥派生函数非常有用,例如,一个密码协议一开始只有一个密钥,例如一个共享的密码或者一个有DH密钥交换函数生成的密钥,但是有很多私密的数据需要操作,例如加密和计算MAC的密钥。另一个使用密钥派生函数的场景是密码系统中安全的随机数生成器,它可以从很多个低强度的信息中产生随机的高熵信息,该部分在后续章节中介绍。
13.2 密码增强
13.3 PBKDF2
13.4 bcrypt
13.5 scrypt
13.6 HKDF
HKDF在RFC5869中定义并在之后的相关论文中[Kra10]中详细介绍,它是一个针对高强度输入的密钥派生算法,比方说通过DH密钥交换的共享的密钥。它不是为了低强度的比方说密码存储这样的设计的。
HKDF给了人们一个适当的现成的密钥派生函数。之前密钥派生通常是为了某个无线网络的特定的标准而完成的。这些无线网络方案通常没有额外的HKDF条款,例如salt是一个可选的参数,只不过该领域KDF没有从根本上被攻破,是开始介绍它的最佳场景。
HKDF是基于HMAC的。和HMAC一样,它是基于hash函数的构造,用户可以自己挑选安全的hash函数。
更近一步
HKDF包括两步。第一步被称为提取阶段,会从输入的信息中提取一个固定长度的密钥。第二个阶段被称为扩展阶段,密钥被用来生成一系列随机数密钥。
提取
提取阶段是为了从很巨大数量但是熵比较低的数据中提取出小的但是拥有高熵的信息内容。
提取阶段是对使用salt来计算数据的HMAC
def extract(salt, data):
return hmac(salt, data)
salt值是可选的。如果没有指定salt的值,那么它就是一串和hash函数输出长度相等的0值。salt在技术上是可选的,设计者来评估它的重要性,因为它是完全独立的密钥派生函数(例如,不同的应用,不同的用户)会产生独立的结果。仅仅是很低熵的salt就可以对密钥派生函数产生显著的安全性的提高。
提取阶段也展示出了为什么HKDF不适合用于密码的密钥派生。提取阶段是在做集中熵,而不是增强熵。它是设计用来处理大量的数据只拥有少量的熵,将其压缩到少量的拥有同等熵的数据,而不是用来生成一系列难以计算的拥有很少的熵的密钥。
一些情况下,可以跳过提取这一步。比方说共享的密钥已经具备了所有正确的特性,例如它是一个足够长度的伪随机传,拥有了足够的熵。一些时候,这一步是完全不应该做的,例如处理DH密钥交换的共享密钥。RFC提供了更详细的有关于是否需要省略提取这一步的讨论,但是这通常是不推荐的。
扩展
在扩展阶段,对提取阶段提取得到的随机数据进行扩展,扩展到需要的数据。
扩展阶段也很简单,使用HMAC来产生数据,这一次不在是使用公开的salt,而是使用提取出的密钥,直到足够多的数据被产生。用于进行HMAC的数据是前一步的输出(最开始的时候一个空的串),info参数(默认值为空),用来计算当前长度的计数器。
def expand(key, info=""):
'''使用可选信息info,扩展密钥'''
output=""
for byte in map(chr, range(256)):
output = hmac(key,output + info + byte)
yield output
def get_output(desired_length, key, info=""):
'''从扩展阶段收集输出,直到收集到足够的输出后返回'''
outputs, current_length = [], 0
for output in expand(key, info):
ouputs.append(output)
current_length += len(output)
if current_length >= desired_length:
break
if current_length < desired_length:
raise RuntimeError("Desired length too long")
return "".join(outputs)[:desired_length]
和提取阶段的salt类似,info参数也是完全可选的,但是也可以很大的提升应用的安全性。一些info参数需要包含一些应用相关的上下文来进行密钥派生。和salt一样,它可以使得密钥派生函数在不同的上下文下产生不一样的值,这样可以增强安全性。例如info参数可以包含针对的用户的信息,一部分密钥派生函数的协议就是这样的或者类似这样的。