GCM乘法实施

时间:2012-05-18 15:00:51

标签: encryption cryptography multiplication aes-gcm galois-field

我在GCM SP-800-38D文档here中为块(Alogrithm 1)的乘法建立了一个C代码。第11-12页。

完成代码后,我想知道是否有任何方法可以测试代码。您可以在我提供的代码下面找到附件。请注意,我使用24位块代替128位块,仅用于测试目的。如有必要,我将不胜感激。

void BLK_MUL (u8 *val_1,u8 *val_2, u8 *out_val)
{ 

    u8 xdata R_val = 0xE1;     
    u8 xdata Z_val[3],V_val[3];
    u8 mask_b   = 0x80;        
    u16 i;  u8 j;           
    bit rnd;                

    for(j=0;j<3;j++,++val_2)    
    {
        Z_val[j]=0x00;      
        V_val[j]=*val_2;    
    }       


    for(i=0;i<24;i++)
    { 
        if (*val_1 & mask_b)
        {
            for(j=0;j<3;j++)    
                Z_val[j]^=V_val[j]; 
        }
        if (!(V_val[2] & 0x01))
        {//if LSB of V_val is 0
            for(j=0;j<3;j++)
            { //V_val = rightshift(V_val)
                if (j!=0)
                    if (V_val[2-j] & 0x01)
                        V_val[3-j] |= 0x80;
                V_val[2-j]>>=1;          
            }
        }
        else
        {//if LSB of V_val is 1
            for(j=0;j<3;j++)
            {//V_val = rightshift(V_val)
                if (j!=0)
                    if (V_val[2-j] & 0x01)
                        V_val[3-j] |= 0x80;
                V_val[2-j]>>=1;          
            }
            V_val[0]^=R_val; //V_val = rightshift(V_val) ^ R                
        }
        if(mask_b & 0x01)   { val_1++; rnd=1;}  
        mask_b >>= 1;                       
        if (rnd)    { mask_b=0x80; rnd=0; } 
    }

    STR_CPY(out_val,Z_val,3);   

    return ;        
}


void main()
{

    code unsigned char val_1[3] ={  0x2b,0x7e,0x15  };
    code unsigned char val_2[3] ={  0x39,0x25,0x84  };
    unsigned char out[3];

    BLK_MUL (val_1,val_2,out);

    return;
}

6 个答案:

答案 0 :(得分:8)

在某些时候,您当然必须根据测试向量检查您的代码。但是,您可以执行相当多的测试而无需知道或计算任何测试向量。

首先,GF(2 ^ 128)中的乘法是可交换的。因此你可以计算 任何输入的BLK_MUL(val_1,val_2,out1)和BLK_MUL(val_2,val_1,out2)都应该得到相同的结果。由于您的代码使用val_1和val_2不同,这已经是一个非常好的测试。

然后你可以使用那个乘法是分配的,即。你可以测试一下 (x + y)* z =(x * z)+(y * z),(其中GF(2 ^ 128)中的加法是通过将两个值的相应字节一起计算得到的)。

最后,一旦你实现了整个字段GF(2 ^ 128),你也可以利用它 它的顺序是2 ^ 128-1。即如果你从值x开始然后将它平方128次,那么你应该得到x。


另外一些评论:

使用公式进行测试(仅使用测试向量)的优点是可以轻松运行大量测试。因为以这种方式添加测试相当容易,所以我经常使用稀疏输入进行一些简单的测试(例如,在输入中设置单个位) 第一。如果出现问题,那么这有助于快速识别错误。

您当前的代码使用临时变量作为结果。这确实是一个好主意,因为它可以确保复制安全。我认为一个好的单元测试也应该涵盖这个案例。 即你可能想要计算两次相同的结果:一次输入和输出指向不同的内存位置,一次输出与输入相同的内存。

此外,至少有一个其他答案谈到了优化。我想如果你 重构代码然后你应该寻找有意义的组件来重用,而不是 盲目地寻找看起来像代码片段。因为GF(2 ^ 128)当然是一个领域 在该领域中的加法和乘法是有意义的组成部分。另一个意义 component是多项式x的乘法(这是相当的 常用于加密)。

答案 1 :(得分:5)

可以找到GCM模式的测试向量here,并且有一堆NIST测试向量here

答案 2 :(得分:3)

谢谢@jack和@emboss。根据jack的建议,我对该功能进行了测试,结果证明是正确的。希望这对其他人有用,但仍会欣赏任何建议和更正。 :)见下面的主要代码:

void main()
{

u8 j;
u8 val_1[3] ={  0x2b,0x7e,0x15  };
u8 val_2[3] ={  0x39,0x25,0x84  };
u8 val_3[3] ={  0x23,0x71,0x25  };
u8 val_3_2[3]={ 0x23,0x71,0x25  };
u8 val_4[3] ={  0x33,0x35,0x44  };
u8 val_5[3] ={  0x2e,0x77,0x11  };

u8 out[3];
u8 out_1[3];
u8 out_2[3];
u8 out_3[3];
u8 out_4[3];
u8 out_5[3];

     //PROOF X*Y = Y*X
BLK_MUL (val_1,val_2,out);      //X*Y
BLK_MUL (val_2,val_1,out_1);    //Y*X
            //N.B: out == out_1
     //PROOF (X+Y)*Z = (X*Z)+(Y*Z) 
for(j=0;j<3;j++)    
    val_3[j]^=val_4[j];         //X = X+Y
BLK_MUL (val_3,val_5,out_2);    //(X+Y)*Z
BLK_MUL (val_5,val_3,out_3);    //Z*(X+Y)
            //N.B: out_2 == out_3

BLK_MUL (val_3_2,val_5,out_4);      //X*Z
BLK_MUL (val_4,val_5,out_5);        //Y*Z
for(j=0;j<3;j++)    
    out_4[j]^=out_5[j];         //(X*Z)+(Y*Z)
            //N.B: out_3=out_2=out_4
return;
}

答案 3 :(得分:3)

GF(2 ^ 128)的测试示例可在以下网址找到:PCLMULQDQ CPU instruction,第78页

答案 4 :(得分:2)

我不会评论它是否有效或如何测试,但我可以给你一些编码提示:

  • 函数名称通常是小写
  • 输入数组应为const ...
  • 或者如果你不介意破坏第二个输入数组,那么V_val就是多余的。
  • 您的变量应重命名。调用输入xy以及输出z; R_val应该只是一个常量,Rmask_b只是maskv_val只是v。进行这些更改后,您的代码将突然变得可读。
  • 取出您添加的评论,这些评论是多余的
  • type u8应该是uint8_t(来自stdint.h)或者只是unsigned char。
  • Z_val是多余的;只需使用输出参数z(out_val)
  • 我不知道'xdata'是什么意思
  • ij应为int
  • rnd是多余的
  • 0x011相同 - 后者更具可读性。

  • if (!(V_val[2] & 0x01))条款可以简化为:

    v[2]>>=1;  /* EDIT --- missed this out before */
    for (j=1; j<3; j++)
    {
        if (v[2-j] & 1)
            v[3-j] |= 0x80;
        v[2-j] >>= 1;
    }
    if (v[2] & 1)
        v[0] ^= R;
    
  • 这个东西:

    if(mask_b & 0x01)   { val_1++; rnd=1;}
    mask_b >>= 1;
    if (rnd)    { mask_b=0x80; rnd=0; }
    

    可以简化为(重命名后)

    if ((mask >>= 1) == 0)
    {
        x++;
        mask = 0x80;
    }
    
    • 直接输出到z时,STR_CPY调用是多余的。此外,它的名称看起来像一个字符串副本,这绝对不是所需要的。

EDIT2(第一次编辑在上面的for循环之前添加了缺失行)

您有两个相同循环的副本,一个在if (!(v[2] & 1))子句中,另一个在else子句中。后者(else子句)也有一个v[0] ^= R;。一份副本可以省略。此外,循环特别对待j == 0,这就是我从循环中提取它的原因(但在帖子中省略了它)。然后循环只有2个项目,j == 1和2.

您可以并且可能应该解开剩下的循环来获取:

    v[2] >>= 1;

    if (v[1] & 1) v[2] |= 0x80;
    v[1] >>= 1;

    if (v[0] & 1) v[1] |= 0x80;
    v[0] >>= 1;

    if (v[2] & 1) v[0] ^= R;

答案 5 :(得分:2)

你也有一个数学考虑尝试使用GF(2 ^ 128)代码GF(2 ^ 128)

常量“R_val = 0xE1”特定于GF(2 ^ 128)并反映不可约多项式的低阶指标

    x^128 + x^7 + x^2 + x^1 + 1 

这是GF(2 ^ 128)的流行同构的生成器,经常使用加密方式。

这相当于位向量(1 || 120“0”|| 10000111)。 (其中每个位反映多项式中出现的“x”的幂。)

低位(高位位128消耗平衡溢出位)以big-endian方式写入x87或以little-endian方式表示xE1。

然而对于GF(2 ^ 24),似乎不太可能,先验,

    x^24 + x^7 + x^2 + x^1 + 1 

是不可减少的(但我还没有测试过) 那么众所周知的是用于G(2 ^ 24)的良好,有效的不可约多项式?
根据表http://www.hpl.hp.com/techreports/98/HPL-98-135.pdf,GF(2 ^ 24)的最低/最简单的不可约多项式似乎是

    x^24 + x^4 + x^3 + x^1 + 1

对应于位向量(1 || 16“0”|| 00011011)。

因此,再次因为高阶位消耗平衡溢出,正确的校正因子,你的R_val,用litle-endian方式写的将是R_val = 0xD8

希望你觉得这很有用。