【问题标题】:GCM Multiplication ImplementationGCM乘法实现
【发布时间】:2012-05-26 04:04:00
【问题描述】:

我在 GCM SP-800-38D 文档here 中为块乘法(算法 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;
}

【问题讨论】:

  • if (!(V_val[2] &amp; 0x01)) 子句和它的else 子句中有相同的for 循环。这有充分的理由吗?
  • @WilliamMorris 我在上面的代码中添加了一些 cmets(请检查)。我只是简单地遵循了代码步骤。有更好的选择吗? :)
  • 是的,但重复仍然是多余的。我在下面添加了一些代码审查 cmets 作为答案
  • 这段代码是否计算 GF(2^{128}) 乘法?

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


【解决方案1】:

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

首先,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。 IE。如果你从一个值 x 开始,然后将它平方 128 次,那么你应该得到 x。


一些额外的 cmets:

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

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

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

【讨论】:

  • 有没有我也可以使用的测试向量?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      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;
      }
      

      【讨论】:

        【解决方案5】:

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

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

        • if (!(V_val[2] &amp; 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;
          }
          
          • STR_CPY 调用是多余的,因为您直接输出到 z。而且它的名字看起来像一个字符串副本,这绝对不是我们需要的。

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

        您有两个相同循环的副本,一个在if (!(v[2] &amp; 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;
        

        【讨论】:

        • 感谢Williams,感谢您在上面编写的if (!(V_val[2] &amp; 0x01)) 子句,它不起作用,因为您首先必须在右移一次之前屏蔽V 的LSB。还有j=0 会发生什么,因为您只以j=1 开头。
        • 是的,我错过了 j==0 位 - 现在添加在循环之前。还解开循环,可以说更好。
        【解决方案6】:

        您还有一个数学方面的考虑,试图将 GF(2^128) 代码用于 GF(2^24)

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

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

        它是流行的 GF(2^128) 同构的生成器,通常用于加密。

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

        低位(高位 128 被消耗以平衡溢出的位)以大端方式写入 x87 或以小端方式表示时写入 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,以小端方式编写将是 R_val = 0xD8

        希望你觉得这很有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-08-17
          • 2020-02-05
          • 1970-01-01
          • 2012-07-12
          • 1970-01-01
          • 1970-01-01
          • 2013-02-05
          • 2017-05-23
          相关资源
          最近更新 更多