《嵌入式软硬件梳理》-9.2.4、工作量证明算法Scrypt

内容摘自:【RFC7914】标准文档。

2.4、工作量证明算法Scrypt

1、简介

    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字符串,目的是加大碰撞难度,这是针对彩虹表这种破解方式进行的改进。

2、实现过程

2.1 Scrypt函数实现

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语言版本

2.2 scryptROMix函数

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语言版本

2.3 scryptBlockMix函数

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语言版本

2.4 Salsa函数

#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函数实现过程

3、总结

      scrypt算法在加密数字货币中是用的最多的一种算法,在莱特币和其他一些货币中,Scrypt算法的实现都是一样的,且代码都一样(这样真的好么),其中p = 1024,r = 1,N = 1,dklen = 32。其中N = 1,所消耗的资源是很少的(良心啊),FPGA都可以不用DDR直接用片内的RAM就可以实现。

你可能感兴趣的:(知识梳理)