. . . . 本文的代码严格来说来自于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;
}