SM2 簽名前要進行的預處理操作

    轉載:https://www.xuebuyuan.com/1473900.html

一般情況下,計算數字簽名時應執行以下操作:

1. 計算原始數據的 Hash 值;
2. 將 Hash 值作爲輸入,計算簽名函數的輸出。並不是對原始數據直接簽名,而是對 Hash 值簽名。
    驗證簽名時應執行以下操作:
1. 計算原始數據的 Hash 值;
2. 將 Hash 值和簽名值作爲輸入,計算驗籤函數的輸出,根據輸出判斷簽名爲“有效”或“無效”。
這只是一個簡單描述,實際上 PKCS#1 中規定的簽名和驗簽過程要複雜得多。

    計算 SM2 簽名和驗籤的過程有點特殊,因爲根據國內的行業標準,SM2 簽名算法要和 SM3 Hash 算法搭配使用,並且計算 SM2 簽名的輸入並不是待簽名數據的 SM3 雜湊值,而是一個預處理階段的輸出。
    預處理分爲兩步:
1. 定義一個級聯生成的字節流 T1 = ENTL || ID || a || b || x_G || y_G || x_A || y_A,
   其中 || 表示字節流的拼接,
   ENTL 是用兩個字節表示的簽名者 ID 的比特長度(注意不是字節長度),
   ID 爲簽名者的標識,
   a, b, x_G, y_G 都是標準中給定的值, x_A 和 y_A 是簽名者的公鑰,
   對字節流 T1 計算 SM3 雜湊值,得到的輸出爲 Z = SM3(T1)。
   這一步並沒有用到待簽名數據。
2. 將待簽名數據用 M 表示,將 Z 與待簽名數據級聯,得到 T2 = Z || M,計算 T2 的雜湊值,即    SM3(T2),SM3(T2)纔是 SM2 簽名函數的真正輸入。
   注意預處理中用到的數都採用 big-endian 表示法!

    從上面的預處理過程可以看出,當驗證簽名時,也要經過同樣的預處理過程,將預處理階段的輸出、簽名值作爲驗籤函數的輸入。

    網上有很多 SM3 雜湊算法的開源實現,比如這個網址上提供的:https://github.com/siddontang/pygmcrypto/tree/master/src 。但是一般能找到的 SM3 實現都沒有提供預處理功能,所以是不能直接用作計算 SM2 簽名和驗籤的輸入。在上面網址提供的
SM3 實現基礎上,本文將給出一個預處理的實現。首先從標準中可以查到預處理第 1 步中 a、b、x_G 、 y_G 的值。簽名者的公鑰通常是以結構體的形式給出的,對於加密機,公鑰定義爲
typedef struct ECCrefPublicKey_st
{
  unsigned int bits;
  unsigned char x[64];
  unsigned char y[64];
} ECCrefPublicKey;
對於 Ukey,公鑰定義爲
typedef struct Struct_ECCPUBLICKEYBLOB
{
  ULONG BitLen;
  BYTE XCoordinate[64];
  BYTE YCoordinate[64];
} ECCPUBLICKEYBLOB;
    注意這兩個結構體在本質上是一樣的。ECCPUBLICKEYBLOB 中出現的 ULONG 並不是 unsigned long,在 Ukey 國內標準中將 ULONG 定義爲無符號 32 位整數類型,因此可以把這個 ULONG 看作是 unsigned int。

    由於是結構體,可能會遇到對齊的問題,常見的情況是:硬件廠商在實現接口時是按 1 字節對齊( 即用 #pragma pack (1) 設定),最好看看硬件廠商提供的用戶手冊覈實一下,確保沒有問題。本文中假定上面兩個結構體都已被設爲按 1 字節對齊。
    對於 SM2 算法,公鑰的 X 分量和 Y 分量都是 32 字節長,因爲以 big-endian 方式存放在對應數組中,對應數組大小爲 64 字節,所以數組中前 32 字節的值爲 0, 後 32 字節的值對應於分量。這一點在從數組中提取出分量值時要用到。

    下面給出 SM2 簽名前的預處理的計算 C 程序:

/************************************************** 
* Author: HAN Wei 
* Author's blog: http://blog.csdn.net/henter/ 
* Date: Oct 30th, 2013 
* Description: the following codes demonstrates how to 
               perform SM3 Hash pre-process for SM2 signature 
**************************************************/ 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sm3.h"

/**************************************************
*函數名稱:SM3HashWithPreprocess
*功能: 計算 SM3 雜湊值(可能包含爲滿足 SM2 簽名要求所做的預處理操作)
*參數:
 input[in]                         輸入數據
 input_byte_len[in]                輸入數據的字節長度
 public_key[in]                    簽名者的公鑰
 public_key_byte_len[in]           簽名者公鑰的字節長度
 signer_ID[in]                     簽名者的 ID 值
 signer_ID_byte_len[in]            簽名者 ID 的字節長度
 hash_value[out]                   SM3 雜湊值
 hash_value_byte_len_pointer[out]  指向表示 SM3 雜湊值字節長度變量的指針
*返回值:
    0    成功
    -1   失敗
*備註:
   如果以下四個條件:
   a) 輸入參數 public_key 是空指針;
   b) 輸入參數 public_key_byte_len 的值等於 0;
   c) 輸入參數 signer_ID 是空指針;
   d) 輸入參數 signer_ID_byte_len 的值等於 0。
   中有一個成立,就直接計算輸入數據 input 的 SM3 雜湊值,
   忽略輸入參數 public_key, public_key_byte_len, signer_ID
   和 signer_ID_byte_len,這時不會進行 SM2 算法簽名預處理
   操作。
   如果四個條件全部不成立,才執行 SM2 算法簽名預處理操作,
   預處理計算過程遵循 GM/T 0009《 SM2 密碼使用規範》。
**************************************************/
int SM3HashWithPreprocess(unsigned char *input,
                          unsigned int input_byte_len,
                          unsigned char *public_key,
                          unsigned int public_key_byte_len,
                          unsigned char *signer_ID,
                          unsigned int signer_ID_byte_len,
                          unsigned char *hash_value,
                          unsigned int *hash_value_byte_len_pointer);

/*********************************************************/
int SM3HashWithPreprocess(unsigned char *input,
                          unsigned int input_byte_len,
                          unsigned char *public_key,
                          unsigned int public_key_byte_len,
                          unsigned char *signer_ID,
                          unsigned int signer_ID_byte_len,
                          unsigned char *hash_value,
                          unsigned int *hash_value_byte_len_pointer)
{
  unsigned short ID_bit_len;
  unsigned char *step1_input;
  unsigned int step1_input_byte_len;
  unsigned char step1_output[32];
  unsigned char *step2_input;
  unsigned int step2_input_byte_len;

  unsigned char a[32]={0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
                       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                       0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
                       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC};
        
  unsigned char b[32]={0x28, 0xE9, 0xFA, 0x9E, 0x9D, 0x9F, 0x5E, 0x34,
                       0x4D, 0x5A, 0x9E, 0x4B, 0xCF, 0x65, 0x09, 0xA7,
                       0xF3, 0x97, 0x89, 0xF5, 0x15, 0xAB, 0x8F, 0x92,
                       0xDD, 0xBC, 0xBD, 0x41, 0x4D, 0x94, 0xE,  0x93};

  unsigned char x_G[32]={0x32, 0xC4, 0xAE, 0x2C, 0x1F, 0x19, 0x81, 0x19,
                         0x5F, 0x99, 0x4,  0x46, 0x6A, 0x39, 0xC9, 0x94,
                         0x8F, 0xE3, 0xB,  0xBF, 0xF2, 0x66, 0xB,  0xE1,
                         0x71, 0x5A, 0x45, 0x89, 0x33, 0x4C, 0x74, 0xC7};

  unsigned char y_G[32]={0xBC, 0x37, 0x36, 0xA2, 0xF4, 0xF6, 0x77, 0x9C,
                         0x59, 0xBD, 0xCE, 0xE3, 0x6B, 0x69, 0x21, 0x53,
                         0xD0, 0xA9, 0x87, 0x7C, 0xC6, 0x2A, 0x47, 0x40,
                         0x2,  0xDF, 0x32, 0xE5, 0x21, 0x39, 0xF0, 0xA0};

// 下面定義的結構體 x 用於判斷當前環境是 big-endian 還是 little-endian
  union { int i;
          char c[sizeof(int)];
        } x;

  if ( (!public_key) || (!public_key_byte_len) || (!signer_ID) || (!signer_ID_byte_len) )
  {
    sm3(input, input_byte_len, hash_value);
    *hash_value_byte_len_pointer = 32U;
    return 0;
  }

// 下面爲滿足 SM2 簽名的要求,做預處理操作
  step1_input_byte_len = (2 + signer_ID_byte_len + 32 * 6);
  if ( !(step1_input = (unsigned char *)malloc(step1_input_byte_len)) )
  {
#ifdef _DEBUG
    printf("malloc function failed at %s, line %d!\n", __FILE__, __LINE__);
#endif
    return (-1);
  }
 
/* 預處理1 */
  ID_bit_len = (unsigned short)(signer_ID_byte_len*8);

/* 判斷當前環境是 big-endian 還是 little-endian。
   國密規範中要求把 ENTL(用 2 個字節表示的 ID 的比特長度)
   以 big-endian 方式作爲預處理 1 輸入的前兩個字節  */
  x.i = 1;
  if(x.c[0] == 1)  /* little-endian */
  {
    memcpy(step1_input, (unsigned char *)(&ID_bit_len) + 1, 1);
    memcpy((step1_input + 1), (unsigned char *)(&ID_bit_len), 1); 
  }
  else  /* big-endian */
  {
    memcpy(step1_input, (unsigned char *)(&ID_bit_len), 1);
    memcpy((step1_input + 1), (unsigned char *)(&ID_bit_len) + 1, 1);
  }

  memcpy((step1_input + 2), signer_ID, signer_ID_byte_len);
  memcpy((step1_input + 2) + signer_ID_byte_len, a, 32);
  memcpy((step1_input + 2) + signer_ID_byte_len + 32, b, 32);
  memcpy((step1_input + 2 + signer_ID_byte_len + 64), x_G, 32);
  memcpy((step1_input + 2 + signer_ID_byte_len + 96), y_G, 32);
  memcpy((step1_input + 2 + signer_ID_byte_len + 128), (public_key + 4 + 32), 32);
  memcpy((step1_input + 2 + signer_ID_byte_len + 160), (public_key + 4 + 64 + 32), 32);
  sm3(step1_input, step1_input_byte_len, step1_output);

/* 預處理2 */
  step2_input_byte_len = (32 + input_byte_len);
  if ( !(step2_input = (unsigned char *)malloc(step2_input_byte_len)) )
  {
#ifdef _DEBUG
    printf("malloc function failed at %s, line %d!\n", __FILE__, __LINE__);
#endif
    free(step1_input);
    return (-1);
  }
  memcpy(step2_input, step1_output, 32);
  memcpy((step2_input + 32), input, input_byte_len);
  sm3(step2_input, step2_input_byte_len, hash_value);
  *hash_value_byte_len_pointer = 32U;
  
  free(step1_input);
  free(step2_input);
  return 0;
}

     編譯時要用到文件 sm3.h 和 sm3.c,這兩個文件的下載網址前面已經給出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章