轉載: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,這兩個文件的下載網址前面已經給出。