前言
SM2国密算法计算签名值之前,需要进行SM3摘要计算,而SM3摘要计算有2个预处理步骤,本文主要描述SM3带公钥摘要如何计算
一、SM3摘要说明
与国际摘要算法,如SHA1、SHA256、SHA512不同,在进行sm2签名时,计算sm3摘要,要进行预处理,其中用户的公钥要参与计算,这一点与国际签名算法不同,比较另类。而且网上很少有对该过程进行详细描述,想查找相关的资料或者代码都挺麻烦的,因此在这篇博文中进行详细说明
SM2签名时摘要计算过程可以参考国家标准《GBT 35276-2017 信息安全技术 SM2密码算法使用规范》第8章节,截图如下
如上图所示,预处理分为2个步骤
1.计算Z值。计算Z值需要用户公钥参与,输入参数1为用户身份标识ID,在标准规范后面有说明,无特殊约定情况下,固定为"1234567812345678"; 输入参数2为用户公钥,如上图中的Xa、Ya。至于图中其他参数,a、b、Xg、Yg是ECC椭圆曲线固定参数,后文代码中进行说明。最终计算结果以Z表示,为固定32字节的摘要值
2.计算最终的杂凑值H。使用Z值和原文数据M计算SM3摘要
二、使用C/C++计算SM3摘要
1.代码
#include <gcrypt.h>
#include <iostream>
#include "cppcodec/base64_rfc4648.hpp"
#include "cppcodec/hex_upper.hpp"
#include <algorithm>
using base64 = cppcodec::base64_rfc4648;
using hex = cppcodec::hex_upper;
int main(int argc, char** argv){
gcry_md_algos md_algos = GCRY_MD_SM3;
int sm3Len = gcry_md_get_algo_dlen(md_algos);
assert(sm3Len == 32);
gcry_md_hd_t hd = nullptr;
gcry_error_t error = gcry_md_open(&hd, md_algos, 0);
if (error != GPG_ERR_NO_ERROR) {
fprintf(stderr, "gcry_md_open error\n");
exit(1);
}
const char* userId = "1234567812345678";
const char* a = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC";
const char* b = "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93";
const char* Gx = "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7";
const char* Gy = "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0";
const char* userX = "E2A0EAE784EE407D6F56A0BD3267D373EAC550E7ACE7588D1E95DDB28FA64D0E";
const char* userY = "F78F7721589D64B53D4DFAA66C3C97851C3C1F2A11AEEBE491D10102FC081DEF";
// 计算预处理1中entl的值: 由2字节表示的 userId 的比特长度
int bitLen = strlen(userId) * 8;
int intBytes = 4, cutIndex = 2;
uint8_t* t = (uint8_t*) &bitLen;
std::vector<uint8_t> entl_;
for (int j = 0; j < intBytes; ++j) {
entl_.push_back(t[j]);
}
// 字节数组元素逆序,因为产生的字节数组顺序是反的
std::reverse(entl_.begin(), entl_.end());
std::vector<uint8_t> entl(entl_.begin() + cutIndex, entl_.end());
const std::vector<uint8_t>& aVector = hex::decode(a, strlen(a));
const std::vector<uint8_t>& bVector = hex::decode(b, strlen(b));
const std::vector<uint8_t>& GxVector = hex::decode(Gx, strlen(Gx));
const std::vector<uint8_t>& GyVector = hex::decode(Gy, strlen(Gy));
const std::vector<uint8_t>& userXVector = hex::decode(userX, strlen(userX));
const std::vector<uint8_t>& userYVector = hex::decode(userY, strlen(userY));
gcry_md_write(hd, entl.data(), entl.size());
gcry_md_write(hd, userId, strlen(userId));
gcry_md_write(hd, aVector.data(), aVector.size());
gcry_md_write(hd, bVector.data(), bVector.size());
gcry_md_write(hd, GxVector.data(), GxVector.size());
gcry_md_write(hd, GyVector.data(), GyVector.size());
gcry_md_write(hd, userXVector.data(), userXVector.size());
gcry_md_write(hd, userYVector.data(), userYVector.size());
uint8_t* z = gcry_md_read(hd, md_algos);
// 定义字节数组,使用内存拷贝复制z的值。不能在计算SM3(Z||M)时直接使用z,而要使用zBytes
uint8_t zBytes[sm3Len];
memcpy(zBytes, z, sm3Len);
std::cout << "Z: " << hex::encode(z, sm3Len) << std::endl;
gcry_md_reset(hd);
const char* buffer = "1234567890";
size_t length = strlen(buffer);
gcry_md_write(hd, zBytes, sm3Len);
gcry_md_write(hd, buffer, length);
uint8_t* hash = gcry_md_read(hd, md_algos);
std::cout << "十六进制输出: " << hex::encode(hash, sm3Len) << std::endl;
gcry_md_close(hd);
return 0;
}
2.说明
SM2算法椭圆曲线方程: y 2 = x 3 + a x + b y^2= x^3 + ax + b y2=x3+ax+b
- 22行参数为用户身份标识,即规范中的ID参数,固定值 1234567812345678
- 23行a为方程中的系数a,以十六进制字符串形式赋值给a变量,固定值 FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
- 24行b为方程中的系数b,以十六进制字符串形式赋值给b变量,固定值 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
- 25行Gx为椭圆曲线的基点x坐标,以十六进制字符串形式赋值给Gx变量,固定值 32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
- 26行Gy为椭圆曲线的基点y坐标,以十六进制字符串形式赋值给Gx变量,固定值 BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
- 27、28两行是用户的公钥x、y坐标,也以十六进制字符串形式赋值给相应变量。这两个参数应该通过参数外传进来
上述代码经过测试,计算结果与java的bouncycastle计算结果一致。当然bouncycastle中并没有sm3带公钥摘要计算的实现,也是本人借鉴公司的代码,以bouncycastle为基础实现的
通过修改用户身份标识ID参数,与bouncycastle计算结果仍保持一致
总结
以上就是今天要讲的内容,本文详细描述了国家标准中SM2签名时的摘要计算流程,以及实例代码,仅供参考