. . . . 本文的代码严格来说来自于libgcrypt源码包中tests目录下的测试程序,我从测试程序中逆向和提取了各个运算流程并自己实现了一遍,达到了调用libgcryt模块进行运算的目的。
. . . . 算法流程的代码其实并不是难点,难点主要在于libgcrypt所使用是s表达式结构的构建,测试程序代码有一部分实现,但是SM2的很少,不够完善,下面的代码中的结构体是我自己编码测试出来的。
. . . . 顺便给自己引个流:Openssl ECC椭圆曲线算法 - 密钥/签名/验签/加密/解密/SM2密文 - 序列化反序列化导出导入 - C源码,相同功能在openssl下的实现
. . . . 废话不多说,上代码
#include <string.h>
#include <stdio.h>
#include <gcrypt.h>
/* 该函数也是提取自tests下测试程序,用来输出s表达式字符串,拿来做展示/检查和逆向非常好用 */
static void
show_sexp(const char *prefix, gcry_sexp_t a)
{
char *buf;
size_t size;
if (prefix)
fputs(prefix, stderr);
size = gcry_sexp_sprint(a, GCRYSEXP_FMT_ADVANCED, NULL, 0);
buf = gcry_xmalloc(size);
gcry_sexp_sprint(a, GCRYSEXP_FMT_ADVANCED, buf, size);
fprintf(stderr, "%.*s", (int)size, buf);
gcry_free(buf);
}
int main()
{
//模拟数据
//来自国标官方的标准SM2数据,包括公私钥和sig签名,digest,加解密用的数据
//公钥q为 04(未压缩标志符号) + 64字节X + 64字节Y 总计130字节
char pkey_sexp[] = "(public-key (ecc (curve sm2p256v1) (q #0409F9DF311E5421A150DD7D161E4BC5C672179FAD1833FC076BB08FF356F35020CCEA490CE26775A52DC6EA718CC1AA600AED05FBF35E084A6632F6072DA9AD13#)))";
//私钥
char skey_sexp[] = "(private-key (ecc (curve sm2p256v1) (q #0409F9DF311E5421A150DD7D161E4BC5C672179FAD1833FC076BB08FF356F35020CCEA490CE26775A52DC6EA718CC1AA600AED05FBF35E084A6632F6072DA9AD13#) (d #3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8#)))";
//签名验签用的原始哈希值,就是签名/待验签的原始数据
char hash_sexp[] = "(data (flags sm2) (value #F0B43E94BA45ACCAACE692ED534382EB17E6AB5A19CE7B31F4486FDFC0D28640#))";
//签名值,包含r和s
char sig_sexp[] = "(sig-val (sm2 (r #F5A03B0648D2C4630EEAC513E1BB81A15944DA3827D5B74143AC7EACEEE720B3#) (s #B1B6AA29DF212FD8763182BC0D421CA1BB9038FD1F7F42D4840B69C485BBC1AA#)))";
// 加解密的,明文的16进制形式,就是"encryption standard"
// char data2enc[] = "(data (flags sm2) (value #656e6372797074696f6e207374616e64617264#))";
// 加解密的自定义字符串
char data2enc[] = "(data (flags sm2) (value \"IM etc TEXsaldvajksdv 76q8762tq487r17624r17^%R@%!#R^E%@^#\"))";
/* 这里有个需要注意的点,仔细观察上面两个s表达式的内容,
* 可以发现当以16进制字符串输入的时候,value后面的数据是用##括起的
* 当以正常字符串形式输入的时候,后面是用""括起的
* 这个区别也会出现在后面解密的明文中
* 可以根据需要自由使用和组合这两种编码
*/
// 加密后的数据,来自于国标官方的内容,abc分别为xy,哈希和密文
char encdata[] = "(enc-val (flags sm2) (sm2 (a #0404EBFC718E8D1798620432268E77FEB6415E2EDE0E073C0F4F640ECD2E149A73E858F9D81E5430A57B36DAAB8F950A3C64E6EE6A63094D99283AFF767E124DF0#) (b #59983C18F809E262923C53AEC295D30383B54E39D609D160AFCB1908D0BD8766#) (c #21886CA989CA9C7D58087307CA93092D651EFA#)))";
//序列化的话只要把数据按照格式填写成这样的字符串,然后调用函数生成为s表达式就可以了
//公钥s表达式生成
gcry_sexp_t pkey;
gcry_error_t err = GPG_ERR_NO_ERROR;
err = gcry_sexp_sscan(&pkey, NULL, pkey_sexp, strlen(pkey_sexp));
if (err)
printf("converting sample Pkey failed: %s\n", gpg_strerror(err));
//私钥s表达式生成
gcry_sexp_t skey;
err = gcry_sexp_sscan(&skey, NULL, skey_sexp, strlen(skey_sexp));
if (err)
printf("converting sample Skey failed: %s\n", gpg_strerror(err));
printf("=================================Sign/Verify=================================\n");
// Hash / Sig 的s表达式生成,tmpsig是我用来自己签名的,可以忽略,如有需要可以参考
gcry_sexp_t TmpSig, sig, hash;
//哈希的S表达式
err = gcry_sexp_sscan(&hash, NULL, hash_sexp, strlen(hash_sexp));
if (err)
printf("converting data failed: %s\n", gpg_strerror(err));
//签名值的s表达式
err = gcry_sexp_sscan(&sig, NULL, sig_sexp, strlen(sig_sexp));
if (err)
printf("converting sig failed: %s\n", gpg_strerror(err));
//自己实现的签名
//给我签!
// err = gcry_pk_sign(&TmpSig, hash, skey);
// if (err)
// printf("converting data failed: %s\n", gpg_strerror(err));
// show_sexp("SIG:\n", TmpSig);
//输出展示
show_sexp("HAS:\n", hash);
show_sexp("SIG:\n", sig);
//自己实现的验签
//给我验!
// err = gcry_pk_verify(TmpSig, hash, pkey);
// if (err)
// printf("gcry_pk_verify failed: %s\n", gpg_strerror(err));
// printf("Wuhu~\n");
//官方的验签
//给我验!
err = gcry_pk_verify(sig, hash, pkey);
if (err)
printf("gcry_pk_verify failed: %s\n", gpg_strerror(err));
printf("Wuhu~\n");
printf("=================================Enc/Dec=================================\n");
gcry_sexp_t result_enc, result_dec;
gcry_sexp_t enc;
//待加密数据的S表达式
err = gcry_sexp_sscan(&enc, NULL, data2enc, strlen(data2enc));
if (err)
printf("converting data failed: %s\n", gpg_strerror(err));
//给我加密!
err = gcry_pk_encrypt(&result_enc, enc, pkey);
if (err)
printf("gcry_pk_encrypt failed: %s\n", gpg_strerror(err));
//展示加密结果
show_sexp("ENC:\n", result_enc);
//标准数据解密,密文s表达式
// gcry_sexp_t encdata_sexp;
// err = gcry_sexp_sscan(&encdata_sexp, NULL, encdata, strlen(encdata));
// if (err)
// printf("converting data failed: %s\n", gpg_strerror(err));
//标准数据部分结束
//解密,注意这里我用的是自己的数据加密出来的内容做解密的
//如果是用国标数据解密,把上面的注释去除,把encdata_sexp替换result_enc即可
err = gcry_pk_decrypt(&result_dec, result_enc, skey);
if (err)
printf("gcry_pk_decrypt failed: %s\n", gpg_strerror(err));
//输出解密结果
show_sexp("DEC:\n", result_dec);
//===============================数据序列化反序列化部分===============================
//注意我这里只反序列化了密文内容,密钥之类的照猫画虎即可
//承载xy / hash / 密文的s表达式
gcry_sexp_t a, b, c;
//从原始的加密结果s表达式中提取对象,之所以是a/b/c,编译完查看一下result_enc的输出就知道了,abc是这三样的标签
a = gcry_sexp_find_token(result_enc, "a", 1);
show_sexp("A:\n", a);
b = gcry_sexp_find_token(result_enc, "b", 1);
show_sexp("B:\n", b);
c = gcry_sexp_find_token(result_enc, "c", 1);
show_sexp("C:\n", c);
//转码用的变量,下面会提到
char temp_array[1024] = {0};
size_t len;
const char *temp_p;
//=== 提取xy,这个函数可以把s表达式转换成字符串指针,只保留数据的部分 ===
temp_p = gcry_sexp_nth_data(a, 1, &len);
// 检查是不是压缩的,04是未压缩的标志
if (*temp_p != 0x04)
{
printf("X/Y are compressed.\n");
return -1;
}
//转码,从上面的判断可以看出xy是字符串形式的,我要16进制格式
int count;
int flag;
for (count = 0; count < 2 * len; count++)
{
flag = count % 2 ? 1 : 16;
switch ((*(unsigned char *)(temp_p + count / 2) / flag) & 0x0F)
{
case 0x00:
temp_array[count] = '0';
break;
case 0x01:
temp_array[count] = '1';
break;
case 0x02:
temp_array[count] = '2';
break;
case 0x03:
temp_array[count] = '3';
break;
case 0x04:
temp_array[count] = '4';
break;
case 0x05:
temp_array[count] = '5';
break;
case 0x06:
temp_array[count] = '6';
break;
case 0x07:
temp_array[count] = '7';
break;
case 0x08:
temp_array[count] = '8';
break;
case 0x09:
temp_array[count] = '9';
break;
case 0x0A:
temp_array[count] = 'A';
break;
case 0x0B:
temp_array[count] = 'B';
break;
case 0x0C:
temp_array[count] = 'C';
break;
case 0x0D:
temp_array[count] = 'D';
break;
case 0x0E:
temp_array[count] = 'E';
break;
case 0x0F:
temp_array[count] = 'F';
break;
default:
printf("WWWWRONG\n");
return -1;
break;
}
}
printf("XY hex len %d, text is %s.\n", 2 * len, temp_array);
//这下就是16进制格式的了,至于从里面提取单独的xy,不用多讲了吧
//清除空间
memset(temp_array, 0, 1024);
//提取哈希值数据
temp_p = gcry_sexp_nth_data(b, 1, &len);
//转存赋值
memcpy(temp_array, temp_p, len);
//检查一下转存赋值对不对,如果需要转码,参考前面的内容
printf("\n\n\n\nB %d:\n%s\n\n\n", len, temp_p);
printf("\n\n\n\nB %d:\n%s\n\n\n", len, temp_array);
//提取密文,步骤同上
memset(temp_array, 0, 1024);
temp_p = gcry_sexp_nth_data(c, 1, &len);
memcpy(temp_array, temp_p, len);
printf("\n\n\n\nC %d:\n%s\n\n\n", len, temp_p);
printf("\n\n\n\nC %d:\n%s\n\n\n", len, temp_array);
printf("Wuhu~\n");
return 0;
}