原文地址:http://rossihwang.farbox.com/post/2014-03-25
原文请参考
The scrypt Password-Based Key Derivation Function
[连接]
1.导言
在密码学中,基于密码的“密钥派生函数”(key derivation functions)被用于从一个密值(secret value)中派生出一个或多个秘钥。多年来,多种基于密码的密钥派生函数已被使用,其中包括最初的DEs-based UNIX Crypt-function,FreeBSD MD5 crpty,PKCS#5 PBKDF2(一般使用SHA-1),GNU SHA-256/512 crypt,Windows NT LAN Manager(NTLM) hash,还有Blowfish-based bcrypt。这些算法都是基于相似的技术,他们都利用了加密学原语,加盐与/或迭代。迭代的次数用于减慢计算。
伴随着计算机系统的加速,迭代的数量也需要增加。合法用户只需花一定的时间去派生密钥,而不必像攻击者那样需要一直提高算力,不过前提是攻击者与合法用户使用同样的软件。然而,正如Bernstein指出,在整数分解的情况下,相对于软件实现并行硬件实现可能不能改变运算的次数。(省略一大段)。美元-秒模型是衡量计算损耗的最合适方法。随着半导体技术发展,电路并不只是变得更快;他们也变得更小,使得在同样资源下,允许更多的并行。总的来说,目前的密钥派生算法,不管迭代次数怎么增加,校验一个密码的时间是一定的,而通过硬件暴力破解去获取密码所花费的(能量或时间)逐年减少。
Scrypt算法目的在于减弱攻击者可以通过并行电路获得的优势,这种定制的并行电路专用于破解密钥派生函数。
余下文章分为几个部分,每一部分都描述一种算法。而这通过几种算法将以递进的方式推出scrypt算法。
2.Salsa20/8 Core函数
Salsa20/8 Core是Salsa20的一种round-reduced变体。他是一个哈希函数,输入64字节字符串,输出64字节字符串。值得注意的是,Salsa20/8 Core不是一种密码学哈希函数,因为他没有抗冲突性(collision-resistant)。
Salsa20 Core的定义
Salsa20的64字节输入从小端模式看就是16个字(word,四个字节)x0,x1,x2,...,x15。这16个字要经过320次可逆的变换,在每次变换中只改变一个字。最终16个字被分别加到原始的x0,x1,x2,...,x15上并对2^32取模,Salsa20(x)以小端模式输出。
每次变换就是将两个字的和的循环移位值(下面的R(a, b))异或到一个字上(第三个字),并对2^32取模。所以这320次变换包含一共320次加法运算,320次异或运算和320次旋转运算。其中旋转运算都是常数距离。
整个转换的过程是一个十次相同的“双循环”序列。每一个“双循环“都是两个循环的序列。每一个循环又包含一组可并行的”四分一循环“。每个”四分一循环“中变换四个字。
完整函数定义如下:
#define R(a, b) (((a) << (b) | (a) >> (32 - (b)))) //交换a的低(b - 32)为与a的高b位交换 void salsa20_word_specification(uint32 out[16], uint32 in[16]) { int i; uint32 x[16]; for(i = 0; i < 16; ++i) { x[i] = in[i]; } for(i = 20; i > 0; i -= 2) { //注:ltc里用的是salsa8,所以这里20应改为8 x[4] ^= R(x[0]+x[12], 7); x[8] ^= R(x[4]+x[0], 9); x[12] ^= R(x[8]+x[4], 13); x[0] ^= R(x[12]+x[ 8], 18); x[9] ^= R(x[5]+x[1], 7); x[13] ^= R(x[9]+x[5], 9); x[1] ^= R(x[13]+x[9], 13); x[5] ^= R(x[1]+x[13], 18); x[14] ^= R(x[10]+x[6], 7); x[2] ^= R(x[14]+x[10], 9); x[6] ^= R(x[2]+x[14], 13); x[10] ^= R(x[6]+x[2], 18); x[3] ^= R(x[15]+x[11], 7); x[7] ^= R(x[3]+x[15], 9); x[11] ^= R(x[7]+x[3], 13); x[15] ^= R(x[11]+x[ 7],18); x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); } for(i = 0; i < 16; ++i) { out[i] = x[i] + in[i]; } }
译者注:从上面第二个for循环可以看出处理都是分为4个数一组处理,如上第一次处理分组为{0, 4, 8, 12},{1, 5, 9, 13}, {2, 6, 10, 14}, {3, 7, 11, 15},第二次处理分组为{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, {12, 13, 14, 15}。所以可以看出第二次处理需要依赖第一次处理的值,很难实现并行
3.scryptBlockMix算法
scryptBlockMix算法,除了它使用了Salsa20/8作为哈希H函数,其他与BlockMix算法基本相同。下面Salsa(T)对应Salsa20/8 Core函数输入是八位向量T。
scryptBlockMix算法
参数:
r 块容量参数(注:只是个参数,不是个数z。ltc协议中r = 1);
输入:
B[0],...,B[2 * r - 1] (2 * r)个64字节块(每个块包含64个字节)输入向量;
输出:
B'[0],...,B'[2 * r - 1] (2 * r)个64字节块(每个块包含64个字节)输出向量;
步骤(伪代码):
1. X = B[2 * r - 1] //取出最后一个Block
2. for i = 0 to 2 * r -1 do
T = X xor B[i] //将所有块分别与上一个Salsa的结果做异或,第一块则跟最后一个块做异或
X = Salsa(T) //算出哈希
Y[i] = X //保存
end for
3.B' = (Y[0], Y[2],..., Y[2 * r - 2],
Y[1], Y[3],..., Y[2 * r - 1]) //打乱顺序,因为ltc中r = 1
//B' = (Y[0], Y[1])
4.scryptROMix算法
scryptROMix算法,除了使用scryptBlockMix作为哈希H函数,其他与ROMix算法基本相同,(and the Integerify function explained inline.)
scryptROMix算法
输入:
r 块容量参数(ltc协议中r = 1);
B 长度为(128 * r)字节的输入向量;
N CPU/内存消耗参数,必须大于1, 它是2的n次幂并且小于2^(128 * r / 8)(在ltc协议中 N = 1024);
输出:
B' 长度为(128 * r)字节的输出向量;
步骤:
1. X = B
2. for i = 0 to N - 1 do
V[i] = X //备份一个X,X是一个(128 * r)个字节的数据
X = scryptBlockMix(X) //scryptBlockMix算法
end for
3.for i = 0 to N - 1 do
j = Integerify(X) mod N //
这里 Integerify (B[0] ... B[2 * r - 1]) 定义为 将 B[2 * r - 1] 转换为小端整数。在ltc算法中,因为r = 1,推出X = {B[0], B[1]},那么Integerify(X)就是把B[1](后64字节转换成小端)
T = X xor V[j]
X = scryptBlockMix(T)
end for
4.B' = X
下面代码选自链接
参照上方伪代码分析
static inline void scrypt_core(uint32_t *X, uint32_t *V) { uint32_t i, j, k; for (i = 0; i < 1024; i++) { // N = 1024 memcpy(&V[i * 32], X, 128); // V[i] = X 注意这里数据按字节写入,128个字节 xor_salsa8(&X[0], &X[16]); // xor_salsa8(&X[16], &X[0]); // 这两行属于scryptBlockMix(X),输出顺序没有打乱? } for (i = 0; i < 1024; i++) { // N = 1024 j = 32 * (X[16] & 1023); // Integerify(X) mod N, 对应之前,数据按字节写入,字节索引要变回字索引,所以要乘以32 for (k = 0; k < 32; k++) { X[k] ^= V[j + k]; // X xor V[j] } xor_salsa8(&X[0], &X[16]); // xor_salsa8(&X[16], &X[0]); // 这两行属于scryptBlockMix(X),输出顺序没有打乱? } }
5.scrypt算法
下面所使用的PBKDF2-HMAC-SHA-256函数表示PBKDF2算法以HMAC-SHA-256算法为PRF(注:PRF是PBKDF2中一个可选模块)。HMAC-SHA-256函数生成32字节输出。
scrypt算法
输入:
P 密码短语,一个字节串
S 盐, 一个字节串(在ltc协议中盐是与输入相同的80个字节)
N CPU/内存消耗参数,必须大于1, 是2的n次幂并且小于2^(128 * r / 8)
r 块容量参数
p 并行化参数,一个小于等于((2^32-1) * hLen) / MFLen的正整数,其中hLen为 32,MFlen是128 * r。(在ltc协议中 p = 1)
dkLen 期望输出的派生密钥的字节长度;一个小于等于(2^32 - 1) * hLen的正整数,其中hLen为32
输出:
DK 派生密钥, 长度为dkLen个字节
步骤:
1. B[0] || B[1] || ... || B[p - 1] = PBKDF2-HMAC-SHA256 (P, S, 1, p * 128 * r)
2. for i = 0 to p - 1 do
B[i] = scryptROMix (r, B[i], N)
end for
3. DK = PBKDF2-HMAC-SHA256 (P, B[0] || B[1] || ... || B[p - 1], 1, dkLen)
补充:
PBKDF2算法
Password-Based Cryptography Specification 参考资料
PBKDF2(P, S, C, dkLen)
选项:
PRF 内含伪随机数函数(hLen表示伪随机数函数输出的字节长度)
输入:
P 密码,一个字节串;
S 盐,一个字节串;
C 迭代次数,一个正整数;
dkLen 期望的密钥字节长度,一个正整数,最大为(2^32 - 1) hLen;
输出:
DK 派生密钥,一个dkLen长字节串;
步骤:
1.若dkLen > (2^32 - 1) * hLen,输出“派生密钥太长”并停止。
2.l是派生密钥中的hLen字节块个数,取整,然后将上一个块的的字节数赋值给r
l = CEIL(dkLen / hLen),
r = dkLen - (1 - 1) * hLen。
其中,CEIL(x)是“ceiling”函数,往上取整
3.对密码P,盐S,迭代数c和块索引使用下面定义的F函数,从而计算出块
T_1 = F(P, S, c, 1),
T_2 = F(P, S, c, 2),
...
T_1 = F(P, S, c, 1),
其中F函数定义如下:
F(P, S, c, i) = U_1 xor U_2 xor ... xor U_c
U_1 = PRF(P, S || INT(i))
U_2 = PRF(P, U_1)
...
U_c = PRF(P, U_{c - 1})
INT(i)是对整数i四字节编码,高位在前
4.合并所有块并提取前dkLen字节生成派生密钥DK:
DK = T_1 || T_2 || ... || Tl<0..r-1>
5.输出派生密钥DK
注意的是,函数F的结构遵循“吊带”原则("belt-and-suspenders" approach)。U_i是通过迭代的形式计算,从而使攻击者无法通过并行来计算;它们(U_i...)全部异或起来为了避免迭代退化成一个小数组(不明白这句,the recursion degenerating into a small set of values)。
HMAC-SHA-256算法
HMAC-SHA-256的定义
Computes a Hash-based Message Authentication Code (HMAC) by using the SHA256 hash function(用SHA256哈希函数计算基于哈希的消息校验码)
US Secure Hash Algorithms 参考资料
SHA-256 参考资料
6.ASN.1语法
7.Salsa20/8测试向量
8.scryptBlockMix测试向量
9.scryptROMix测试向量
10.PBKDF2-HMAC-SHA-256测试向量
11.Scrypt测试向量
补充小知识:
1.抗冲突性(collision resistance): 它是加密学哈希函数的一个特性。若一个哈希函数具有抗冲突性,那么就很难找到两个输入具有同一个输出。