相对于另一种更好的加密实现,本文方法容易受干扰
尽量使用我另一篇博客介绍的https://blog.csdn.net/qq_15509071/article/details/107832587 这个开源密码箱来实现
SM2是一种非对称秘钥加密算法。用最明白的话说:
- 从一个私钥可以生成唯一一个公钥(不考虑随机数,在这里把随机数固定),所以测试工具里先输入私钥再点击生成秘钥对
- 一个公钥可以找出很多私钥
- 加密时输入的参数是:公钥和明文,输出:密文
- 解密时输入参数是:私钥和密文,输出:明文
- 选择一样的曲线,在官网的示例中有两条曲线,最后推荐的又是另一种曲线,网上的很多测试工具都是基于推荐曲线做的。官网链接 http://www.oscca.gov.cn/News/201012/News_1198.htm
- 公钥是坐标点:P(Px,Py)
- 网上的测试工具有的输入输出都是16进制的,有的输入输出都是10进制的,要一致
- 加密时也会用随机数,如果随机数固定,则公钥也是固定的,密文也是固定的
- SM2分为秘钥交换,签名验证,公钥加密,网上的好多代码都是前两个,没有公钥加密,本文写的就是公钥加密
- 我用的sm2的c语言代码的下载地址是:http://download.csdn.net/detail/jk0o0/7834347#comment
- 密文分为C1,C2,C3,三部分,C1长度是64,C2是明文的长度,C3是32位
- 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)