在iOS中调用C语言的国密算法SM2以替换RSA



相对于另一种更好的加密实现,本文方法容易受干扰
尽量使用我另一篇博客介绍的https://blog.csdn.net/qq_15509071/article/details/107832587 这个开源密码箱来实现








SM2是一种非对称秘钥加密算法。用最明白的话说:

  1. 从一个私钥可以生成唯一一个公钥(不考虑随机数,在这里把随机数固定),所以测试工具里先输入私钥再点击生成秘钥对
  2. 一个公钥可以找出很多私钥
  3. 加密时输入的参数是:公钥和明文,输出:密文
  4. 解密时输入参数是:私钥和密文,输出:明文
  5. 选择一样的曲线,在官网的示例中有两条曲线,最后推荐的又是另一种曲线,网上的很多测试工具都是基于推荐曲线做的。官网链接 http://www.oscca.gov.cn/News/201012/News_1198.htm
  6. 公钥是坐标点:P(Px,Py)
  7. 网上的测试工具有的输入输出都是16进制的,有的输入输出都是10进制的,要一致
  8. 加密时也会用随机数,如果随机数固定,则公钥也是固定的,密文也是固定的
  9. SM2分为秘钥交换,签名验证,公钥加密,网上的好多代码都是前两个,没有公钥加密,本文写的就是公钥加密
  10. 我用的sm2的c语言代码的下载地址是:http://download.csdn.net/detail/jk0o0/7834347#comment
  11. 密文分为C1,C2,C3,三部分,C1长度是64,C2是明文的长度,C3是32位
  12. C1 || C2 || C3 的意思就是拼在一起,而不是做什么或运算


现在把第10点的代码集合到自己的工程:

1.将不需要的文件删除,即下图红方框里的文件

这里写图片描述

注意:这些文件的编码格式不是utf8的,在xcode里面中文会显示成乱码,这些中文注释一定要看,在Windows系统下看。

2.导入到ios工程里,编译报错,没有openssl/ec.h这个文件
这里写图片描述

3.查一下什么是openssl,是关于密码的第三方开源库,然后需要把它集成到我们工程里。

3.1到https://github.com/x2on/OpenSSL-for-iPhone下载下来,是1.0.2版本的。下载后的文件是:
这里写图片描述

3.2只看build-libssl.sh文件,打开mac电脑的终端程序,将这个sh文件直接拖到终端里
这里写图片描述

3.3点回车,它会自动下载openssl的源代码并生成多个指令集的静态库。当然需要10分钟左右时间
这里写图片描述

3.4 openssl的源代码和编译好的静态库在mac电脑的根目录下: /Users/用户名/OpenSSL_1_0_2h.tar.gz
这里写图片描述

3.5在3.4的bin目录下就是编译好的静态库,包括
这里写图片描述

3.6 以iPhoneOS9.3-arm64.sdk文件夹为例:
这里写图片描述

3.7 lib目录下的libcrypto.a和libssl.a是生成的静态库,include目录下的openssl文件夹是对应的头文件。
这里写图片描述
这里写图片描述

3.8这个是arm64的库,如果同时需要支持armv7 arm64 则要将两个静态库合成一个,用命令:lipo -create /Users/yyy/Desktop/合到一起/libcrypto7.a /Users/yyy/Desktop/合到一起/libcrypto64.a -output /Users/yyy/Desktop/合到一起/libcrypto.a

3.9将这三个文件导入到我们工程里,编译一下,报错还是和之前一样:‘openssl/ec.h’ file not found。点击xcode工程的搜索和替换,填写下面信息,点全部替换

这里写图片描述

4编译一下,报错
duplicate symbol _main in: SM2.o main.o

5.将sm2.c里面的main函数改名为mianSM2, 现在编译通过。
这里写图片描述

上面的中文注释很重要!只看part4是SM2公钥加密,

这四个是官网的示例曲线
sm2_param_fp_192, 
sm2_param_fp_256, 
sm2_param_f2m_193,
sm2_param_f2m_257,
这个是官网推荐曲线,用这个
sm2_param_recommand

在工程里需要的地方调用
test_part4(sm2_param_recommand, TYPE_GFp, 256);
这个方法。

5在编译可能会报错

这里写图片描述

把RSA改成RSA_Y
6.这是控制台的输出:

key_B->d:
1649AB77 A00637BD 5E2EFE28 3FBF3535 34AA7F7C B89463F2 08DDBC29 20BB0DA0 
key_B->P->x:
191BFF81 48006EEA 72D857CB 974DB9F4 903B3CA3 655D8D59 7AD4663F 5044DCB1 
key_B->P->y:
E2F7888A F1FCD8C6 53A8059C D2F37985 5389F71A 7709E2C1 EE1E914C 855EF119 
(BYTE *)H:
B2054BCB 433B430C F6141BCF 2C98F617 7C78C6E5 ED5F953E E92B1F70 AAF70233 
encrypt: 
message_data->C_2:
D76B28B9 3A4B3765 997A3BBC 58F99873 1D0AA2
d:
1649AB77 A00637BD 5E2EFE28 3FBF3535 34AA7F7C B89463F2 08DDBC29 20BB0DA0 
xy2->x:
B18FE085 4DAF664D 357BD2DA 38714F02 026CF4A7 62BEFF0C DEFEE1AF 002DA0EE 
xy2->y:
38ED9760 EF652F28 B81732B9 6247E135 87642E30 D9DFA9B3 C307A092 E415B07F 
(BYTE *)H:
B2054BCB 433B430C F6141BCF 2C98F617 7C78C6E5 ED5F953E E92B1F70 AAF70233 
decrypt: len: 19
encryption standard

key_B->d:私钥
key_B->P->x:公钥x
key_B->P->y:公钥y
(BYTE *)H:t
message_data->C_2:C2

有几个问题:
1.输出的长度不全(输出C,C1时)
2.这个方法加密解密是放在一起的
3.明文输入的是字符串,而不是16进制的char数组

7.把这个方法分成加密和解密两个方法

void sm2JiaMi(char **sm2_param, int type, int point_bit_length , char *mingwen,char *miwen)

{
    ec_param *ecp;
    sm2_ec_key *key_B;
    message_st message_data;

    ecp = ec_param_new();
    ec_param_init(ecp, sm2_param, type, point_bit_length);
    key_B = sm2_ec_key_new(ecp);
 
//用私钥和随机数导出一个公钥,实际应用时没有私钥,也就是没有这行代码,直接设置下面的公钥
    sm2_ec_key_init(key_B, sm2_param_d_B[ecp->type], ecp);//把中间的值给key_b的b

    
    memset(&message_data, 0, sizeof(message_data));
//设置明文 这里输入一个字符串 如果输入char[]需要稍微改动
    message_data.message = (BYTE *)mingwen;

    message_data.message_byte_length = (int)strlen((char *)message_data.message);
    message_data.klen_bit = message_data.message_byte_length * 8;

    
//随机数 拷贝到message_data.k,实际使用时应该随机生成这个数
    sm2_hex2bin((BYTE *)sm2_param_k[ecp->type], message_data.k, ecp->point_byte_length);

//设置公钥
    sm2_bn2bin(key_B->P->x, message_data.public_key.x, ecp->point_byte_length);
    sm2_bn2bin(key_B->P->y, message_data.public_key.y, ecp->point_byte_length);
    DEFINE_SHOW_BIGNUM(key_B->P->x);//公钥PB =(xB ,yB ): 坐标xB :
    DEFINE_SHOW_BIGNUM(key_B->P->y);//坐标yB :
//加密
    sm2_encrypt(ecp, &message_data);
    memcpy(miwen, message_data.C, sizeof(message_data.C));

    
    sm2_ec_key_free(key_B);
    ec_param_free(ecp);

}
void sm2Jiemi(char **sm2_param, int type, int point_bit_length , char *miwen ,char output[] ){
    ec_param *ecp;
    sm2_ec_key *key_B;
    message_st message_data;
    //ecp的开辟空间p a b n
    ecp = ec_param_new();
    //ecp 给 pabn设置标准值
    ec_param_init(ecp, sm2_param, type, point_bit_length);
    //给dp开辟空间
    key_B = sm2_ec_key_new(ecp);
    //设置私钥,把中间的值给key_b的b
    sm2_ec_key_init(key_B, sm2_param_d_B[ecp->type], ecp);
    memset(&message_data, 0, sizeof(message_data));
    //明文的长度,这个长度应该根据密文计算,这里固定写6
    message_data.message_byte_length = 6;
    //k的比特长度是明文长度*8
    message_data.klen_bit = message_data.message_byte_length * 8;
 
    //设置私钥,解密和公钥和随机数无关
    sm2_bn2bin(key_B->d, message_data.private_key, ecp->point_byte_length);
    //私钥dB :
    DEFINE_SHOW_BIGNUM(key_B->d);
    //给解密后的明文开辟空间
    message_data.decrypt = (BYTE *)OPENSSL_malloc(message_data.message_byte_length + 1);
    memset(message_data.decrypt, 0, message_data.message_byte_length+1);//置为0

    //设置密文
    for (int i = 0; i < 256; i++)
    {
        message_data.C[ i] =  miwen[i];
    }

    DEFINE_SHOW_STRING(message_data.C, 256);

    sm2_decrypt(ecp, &message_data);

    memcpy(output, message_data.decrypt, 100);
    
    OPENSSL_free(message_data.decrypt);

    sm2_ec_key_free(key_B);
    ec_param_free(ecp);
}
这是如何在ios工程调用上面两个方法
    NSString *mingwen = @"123456";
    char miwen[1024];
    sm2JiaMi(sm2_param_recommand, TYPE_GFp, 256, [mingwen UTF8String], miwen);
    //密文前面多个04  在用其他工具对密文解密时需要去掉
    NSData *miwendata =  [[NSData alloc]initWithBytes:miwen length: mingwen.length+32+64 +2];
    NSLog(@"密文data=%@",  miwendata );
    
    //解密和加密类似将char数组转成nsdata再转成nsstring
    char output[100];
    sm2Jiemi(sm2_param_recommand, TYPE_GFp, 256, miwen,output);
    NSString *mingwenout = [[NSString alloc]initWithCString:output encoding:NSUTF8StringEncoding];
    NSLog(@"---解密后%@---",mingwenout);

如果需要传入自己公钥加密,则加密方法要相应改一下

//使用传入的公钥加密
void sm2JiaMiWithPublicKey(char **sm2_param, int type, int point_bit_length , char mingwen[],char *miwen,unsigned char px[],unsigned char py[]){
    
    ec_param *ecp;
    sm2_ec_key *key_B;
    message_st message_data;
    
    ecp = ec_param_new();
    ec_param_init(ecp, sm2_param, type, point_bit_length);
    
    key_B = sm2_ec_key_new(ecp);
    
    sm2_ec_key_init(key_B, sm2_param_d_B[ecp->type], ecp);
    
    
    memset(&message_data, 0, sizeof(message_data));
    
    message_data.message = (BYTE*)mingwen;
//    memcpy(message_data.message, mingwen,strlen(mingwen) );
    
    
    message_data.message_byte_length = 8;
    
    
    message_data.klen_bit = message_data.message_byte_length * 8;
    
    //这个是固定的随机数
    //sm2_hex2bin((BYTE *)sm2_param_k[ecp->type], message_data.k, ecp->point_byte_length);
    
    //随机数种子
    static const char rnd_seed[] = "random num c random num seed random num c random num seed";

    RAND_seed(rnd_seed, sizeof rnd_seed);
    unsigned char suijishu[32];
    //生成随机数
    RAND_pseudo_bytes(suijishu,32);
    for( int i=0;i<sizeof suijishu;i++){
        //printf("%02x", suijishu[i]);
        message_data.k[i]=suijishu[i];
    }
    printf("\n");
    DEFINE_SHOW_STRING(message_data.k, sizeof(message_data.k));
    
    
    
    //设置px
    //printf("px\n");
    for( int i=0;i<32;i++){
        //printf("%02x", px[i]);
        message_data.public_key.x[i]=px[i];
    }
    //printf("\n");
    
    //设置py
    //printf("py\n");
    for( int i=0;i<32;i++){
        //printf("%02x", py[i]);
        message_data.public_key.y[i]=py[i];
    }
    //printf("\n");
    
    
    DEFINE_SHOW_BIGNUM(key_B->P->x);//公钥PB =(xB ,yB ): 坐标xB :
    DEFINE_SHOW_BIGNUM(key_B->P->y);//坐标yB :
    DEFINE_SHOW_STRING(message_data.public_key.x, 32);
    DEFINE_SHOW_STRING(message_data.public_key.y, 32);
    
    sm2_encrypt(ecp, &message_data);
    
    memcpy(miwen, message_data.C, sizeof(message_data.C));
    
    sm2_ec_key_free(key_B);
    ec_param_free(ecp);
}


调用方法是

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //使用固定的公钥加密
//    NSString *mingwen = @"123456";
//    char miwen[1024];
//    sm2JiaMi(sm2_param_recommand, TYPE_GFp, 256, [mingwen UTF8String], miwen);
//    //密文前面多个04  在用其他工具对密文解密时需要去掉
//    NSData *miwendata =  [[NSData alloc]initWithBytes:miwen length: mingwen.length+32+64 +2];
//    NSLog(@"密文data=%@",  miwendata );
    
    //使用自己已知的公钥加密
    NSString *mingwen = @"123456";
    char miwen[1024];
    NSString *px_ = [@"F5AB4BCC 007AF4C3 862CF413 57C035AE 090B39B3 A7204E2D E888753E 99EC507A" stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSString *py_ = [@"BE394FC1 0F50FC59 F6586DF7 B493150E 5DF7F575 BC1214FE D849E967 D15993FF" stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSData *px_data = [self dataFromHexString:px_];
    NSData *py_data = [self dataFromHexString:py_];
    sm2JiaMiWithPublicKey(sm2_param_recommand, TYPE_GFp, 256, [mingwen UTF8String], miwen, px_data.bytes,py_data.bytes);
    //密文前面多个04  在用其他工具对密文解密时需要去掉
    NSData *miwendata = [[NSData alloc]initWithBytes:miwen length: mingwen.length+32+64 +2];
    NSLog(@"密文data=%@", miwendata );
    
    
    //解密和加密类似将char数组转成nsdata再转成nsstring
    char output[100];
    sm2Jiemi(sm2_param_recommand, TYPE_GFp, 256, miwen,output);
    NSString *mingwenout = [[NSString alloc]initWithCString:output encoding:NSUTF8StringEncoding];
    NSLog(@"---解密后%@---",mingwenout);
    return YES;
}
- (NSData *)dataFromHexString:(NSString *)input {
    const char *chars = [input UTF8String];
    int i = 0;
    NSUInteger len = input.length;
    
    NSMutableData *data = [NSMutableData dataWithCapacity:len / 2];
    char byteChars[3] = {'\0','\0','\0'};
    unsigned long wholeByte;
    
    while (i < len) {
        byteChars[0] = chars[i++];
        byteChars[1] = chars[i++];
        wholeByte = strtoul(byteChars, NULL, 16);
        [data appendBytes:&wholeByte length:1];
    }
    return data;
}

发现有崩溃,把char miwen[100] 改成 char miwen[1024]即可,文章中已修改(20170112)

// 增加使用自定义私钥解密,加解密时04的处理 明文不限制位数
http://download.csdn.net/detail/qq_15509071/9784753 (20170317)

.
.

整合后的最新代码20181108

https://github.com/XiaoHeHe1/SM2_Encrypt_in_iOS/tree/master

注:如使用最新版本的openssl,工程需要有一些小改动(20211022)

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 39
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值