内容摘自:【RFC7914】标准文档。
Scrypt算法是基于PBKDF2-HMAC-SHA-256算法之上的安全加密算法。在PBKDF2-HMAC-SHA-256算法基础上,对输入数据又进行p次迭代(p<= ((2^32 - 1)*hlen/MFlen; hlen = 32,MFlen = 128*r))。Scrypt算法的参数中有一个N参数,该参数用于增加内存开销的,例如输入数据为B[128]字符串,那么当N=1时,需要增加128个字节作为中间变量运算,当N=1024时,需要增加128*1024个字节作为中间变量运算,因此该算法可以有效的利用该变量增加内存开销。上面提到的p变量用于迭代次数的控制,可以利用该参数增加算法运行时间和阻止ASIC矿机的实现,不过如果该参数设置太大,则工作量证明验证时间太长,如果设置太小,又阻止不了ASIC矿机的低成本实现,因此这个变量是一个需要权衡的变量。
莱特币(LTC)的工作量证明算法中,p=1024,r = 1,N = 1,这样的配置在与比特币同样的资源下显著降低ASIC的验证速度。此外,PBKDF2-HMAC-SHA-256算法是在SHA-256的基础上增加了password字符串和salt字符串,目的是加大碰撞难度,这是针对彩虹表这种破解方式进行的改进。
Algorithm scrypt
Input:
P Passphrase, an octet string.
S Salt, an octet string.
N CPU/Memory cost parameter, must be larger than 1,
a power of 2, and less than 2^(128 * r / 8).
r Block size parameter.
p Parallelization parameter, a positive integer
less than or equal to ((2^32-1) * hLen) / MFLen
where hLen is 32 and MFlen is 128 * r.
dkLen Intended output length in octets of the derived
key; a positive integer less than or equal to
(2^32 - 1) * hLen where hLen is 32.
Output:
DK Derived key, of length dkLen octets.
Steps:
1. Initialize an array B consisting of p blocks of 128 * r octets
each:
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)
图1_1 Scrypt算法的主体实现过程
上图是【RFC7914】文档中的摘录,语法是VFP(Visual FoxPro)语言格式,不过很好理解具体的运算过程,首先先了解一下输入参数:
P: 密码段,这是一个字符串,例如“htc123456”,该字段是PBKDF2-HMAC-SHA-256算法必须要的参数。
S: Salt,中文的意思是盐,也是一个字符串,例如“hellohacker”,该字段对那些破解算法的人来说就好像在伤口上撒盐一样(拙见)。
N: 内存成本控制字段,范围1 < N < 2^(128 * r / 8),且必须是2的整数倍。在简介中,我也分析了这个变量。
r: 块大小控制字段,该字段用于控制输入字符串的单个块的字节数量,因为字节输入算法中是要被分为一块一块的,这个就是块大小。
p: 从上面翻译结果是并行化参数,对于FPGA来说,如果你想在单个上升沿就得到一个结果的话,你必须需要并行增加p个模块,这样会大大增加消耗FPGA的资源面积,因此这个值越大,FPGA实现的成本就越高。
dklen: 输出结果长度控制字段,该字段控制输出结果的长度,例如dklen = 32,那么输出结果就是256bits。
输出数据:
DK: 目标数据,长度受dklen控制。
改成C语言版本如下:
/*该函数只是作为说明来使用,不能直接拿去编译,如果要编译其中N,r,p必须是固定值,因为要定义数组*/
void Scrypt(const char* P,int p_len,const char* S,int s_len,int N,int r,uint32_t p,uint32_t dklen,char* DK)
{
uint8_t B[128*r][p];//r,p必须实现固定,可以用宏定义
/*第一次HASH*/
PBKDF2_HMAC_SHA256(P,p_len,S,s_len,1,&B[128*r][0],p*128*r);//输出结果到B[128*r][p]
/*p轮迭代*/
for(int i = 0;i < p;i++)
{
scryptROMix(r,B[128*r][i],N,B[128*r][i]);
}
/*第二次HASH*/
PBKDF2_HMAC_SHA256(P,p_len,&B[128*r][0],p*128*r,1,32,DK);//输出长度为32,值为DK
}
图1_2 Scrypt算法的主体实现过程C语言版本
Algorithm scryptROMix
Input:
r Block size parameter.
B Input octet vector of length 128 * r octets.
N CPU/Memory cost parameter, must be larger than 1,
a power of 2, and less than 2^(128 * r / 8).
Output:
B’ Output octet vector of length 128 * r octets.
Steps:
1. X = B
2. for i = 0 to N - 1 do
V[i] = X
X = scryptBlockMix (X)
end for
3. for i = 0 to N - 1 do
j = Integerify (X) mod N
where Integerify (B[0] ... B[2 * r - 1]) is defined
as the result of interpreting B[2 * r - 1] as a
little-endian integer.
T = X xor V[j]
X = scryptBlockMix (T)
end for
4. B’ = X
图2_1 Scrypt算法中的scryptROMix函数实现过程
函数说明:
对输入的B[128*r]数据进行变换运算,先计算一个V[128*r][N]数组出来,然后再迭代N轮输出原长度的B’[128*r]数据来。
输入参数:
N: 内存成本控制字段,范围1 < N < 2^(128 * r / 8),且必须是2的整数倍。在简介中,我也分析了这个变量。
B: 输入数据,长度为128*r。
r: 块大小控制字段,该字段用于控制输入字符串的单个块的字节数量,因为字节输入算法中是要被分为一块一块的,这个就是块大小。
输出数据:
B’: 输出数据,长度为128*r。
改成C语言版本如下:
/*该函数只是作为说明来使用,不能直接拿去编译,如果要编译其中N,r,p必须是固定值,因为要定义数组*/
void scryptROMix(int N,int r,const char* B,char* B_1)
{
uint8_t X[128*r];
uint8_t T[128*r];
uint8_t V[128*r][N];
/*B赋值给X,X = B*/
memcpy(X,B,128*r);
/*生成V[128*r][N]*/
for(int i = 0; i < N; i++)
{
memcpy(V[i],X,128*r);
scryptBlockMix(X,X);//第一个X作为输入,第二个X作为输出
}
/*迭代运算*/
for(int i = 0; i < N; i++)
{
int j = Integerify(X) % N;
memcpy(T,X^V[j],128*r);
memcpy(X,scryptBlockMix(T),128*r);
}
/*输出结果*/
memcpy(B_1,X,128*r);
}
图2_2 Scrypt算法中的scryptROMix函数实现过程C语言版本
Algorithm scryptBlockMix
Parameters:
r Block size parameter.
Input:
B[0] || B[1] || ... || B[2 * r - 1]
Input octet string (of size 128 * r octets),
treated as 2 * r 64-octet blocks,
where each element in B is a 64-octet block.
Output:
B’[0] || B’[1] || ... || B’[2 * r - 1]
Output octet string.
Steps:
1. X = B[2 * r - 1]
2. for i = 0 to 2 * r - 1 do
T = X xor B[i]
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])
图2_2 Scrypt算法中的scryptBlockMix函数实现过程
函数说明:
该函数的作用是对输入块进行各种异或移位运算,目的是增强最终的HASH结果值,增加破解难度。
输入参数:
B[2*r]: 输入数据,每个元素的大小是64字节。
输出数据:
B’[2*r]: 输出数据,每个元素的大小是64字节。
改成C语言版本:
/*该函数只是作为说明来使用,不能直接拿去编译,如果要编译其中N,r,p必须是固定值,因为要定义数组*/
void scryptBlockMix(const char B[2*r][64],char B_1[2*r][64])
{
uint8_t X[64];
uint8_t T[64];
uint8_t Y[2*r][64];
/*第一步,把最后的那个元素赋值给X*/
memcpy(X,&B[2*r-1][0],64);
/*迭代运算*/
for(int i = 0; i < 2*r; i++)
{
memcpy(T,X ^ B[i];64);
Salsa(T,X);
memcpy(&Y[i][0],X,64);
}
/*输出结果*/
memcpy(B_1,Y,2*r*64);
}
图2_2 Scrypt算法中的scryptBlockMix函数实现过程C语言版本
#define R(a,b) (((a) << (b)) | ((a) >> (32 - (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 = 8;i > 0;i -= 2) {
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];
}
图4_1 Scrypt算法中的salsa函数实现过程
scrypt算法在加密数字货币中是用的最多的一种算法,在莱特币和其他一些货币中,Scrypt算法的实现都是一样的,且代码都一样(这样真的好么),其中p = 1024,r = 1,N = 1,dklen = 32。其中N = 1,所消耗的资源是很少的(良心啊),FPGA都可以不用DDR直接用片内的RAM就可以实现。