在IOS工程中使用OC调用C语言国密算法SM4(来替换DES算法)

来源:互联网 发布:淘宝客qq群拉人 编辑:程序博客网 时间:2024/06/17 02:38
我为什么写这篇博客呢,在网上可以搜到关于SM4的信息很多,代码基本都是c和java语言的,可是在ios里面和OC语言相互调用的网上还没有搜到。关于SM4算法的官方文档和源代码,见[中国互联网络信息中心官网](http://www.cnnic.net.cn/jscx/mixbz/sm4/)。提示:在上面网站的页面中点击“详见SM4算法”,可能有时打不开,可过几天再试。在实际调试时遇到很多问题,比如进制转换,有哪些输入的参数,输入的参数有什么格式要求,输出的是什么。在网上找到的源代码包含这四个文件: sm4.c sm4.h sm4test.c sms4.c先集成到ios工程中,直接运行报错:
duplicate symbol _main in:    /Users/....../sm4test.o    /Users/....../sms4.o
在sm4test.o和sms4.o中都定义了main函数,需要改名
  1. sm4test.c里面的main()改名为mainTest4()
  2. sms4.c里面的main()改名为mainSms4()

现在工程可以正常的运行起来了,为了调用上面这两个函数,为其添加两个对应的sm4test.h和sms4.h

////  sm4test.h//  keyboardYU////  Created by yfc on 16/7/3.//  Copyright © 2016年 yfc. All rights reserved.//#ifndef sm4test_h#define sm4test_hint mainTest4();#endif /* sm4test_h */
////  sms4.h//  keyboardYU////  Created by yfc on 16/7/3.//  Copyright © 2016年 yfc. All rights reserved.//#ifndef sms4_h#define sms4_hint mainSms4();#endif /* sms4_h */

在工程里需要的地方引入这两个头文件,这里以AppDelegate.m为例
在- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法中调用:

////  AppDelegate.m//  keyboardYU////  Created by yfc on 16/6/27.//  Copyright © 2016年 yfc. All rights reserved.//#import "AppDelegate.h"#import "sm4test.h"#import "sms4.h"@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    mainTest4();    mainSms4();    return YES;}@end

在控制台可以看到运行以上代码输出的是:
这里写图片描述

通过查看官方文档,可以确定:

  1. ECB模式下的明文和秘钥的值都是:01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10
  2. 密文是:68 1e df 34 d2 06 96 5e 86 b3 e9 4f 53 6e 42 46
  3. 对一段明文反复加密100万次:59 52 98 c7 c6 fd 27 1f 04 02 f8 04 c3 3d 3f 66

当时做SM4的加解密时,遇到的问题现在分享给大家:

  1. SM4加密分为ECB和CBC模式,选用的是ECB
  2. SM4的明文必须是16的倍数,查了好多才查到,如果不是16的倍数要做数据填充Padding,不填充的话每次加密出来的数据都不一样,同样解密后也要去掉填充。(秘钥相同,明文相同,填充方式相同则密文一定相同)
  3. 加密时输入的是16位的秘钥和明文,输出密文
  4. 解密时输入的是16位的秘钥和密文,输出明文
  5. 明文和密文的长度是一样的(若不是16的倍数要算填充后的)
  6. 加解密调用的函数都是C的,接收和产生的数据类型都是char数组:unsigned char[],数组内容是16进制的。
  7. 在iOS中函数的返回值可以直接return
    -(NSString *)add:(int)a andb:(int)b{
    return [NSString stringWithFormat:@"%d",a+b];
    }

    在c语言中函数返回值不可以是char[],可以像这样写
    void sm4_crypt_ecb( sm4_context *ctx,
    int mode,
    int length,
    unsigned char *input,
    unsigned char *output)
    input是输入,output是输出,函数内部对output赋值,函数执行后,可以得到output的值。

—–下面对源代码进行改造来和OC相互调用—–

//  1.将NSString类型的数据转成char[]类型    NSString *plainIn = @"this is plain text";    NSData *plainInData =[plainIn dataUsingEncoding:NSUTF8StringEncoding];    int dataLength =plainInData.length;   ;    unsigned char plainInChar[dataLength];//  memcpy函数:将plainInData数据dataLength长度的部分复制到plainInChar里面    memcpy(plainInChar, plainInData.bytes, dataLength);//  说明:1.dataLength这个长度很重要,刚开始时候用过strlen和sizeof,,plainIn.length发现英文情况下没问题,中文的话数据会有缺失//       2.进制转换,要将平时的10进制数据转成16进制,在这里使用NSData它默认就是以16进制保存的,这里省略进制转换的步骤    
//  2.对明文数据进行填充来保证位数是16的倍数,为了少声明一个变量在这里把填充和第1步放在一起    NSString *plainIn = @"this is plain text";    NSData *plainInData =[plainIn dataUsingEncoding:NSUTF8StringEncoding];    int plainInDataLength =plainInData.length;   ;//  p是需要填充的数据也是填充的位数    int p = 16 - plainInDataLength % 16;    unsigned char plainInChar[plainInDataLength + p];    memcpy(plainInChar, plainInData.bytes, plainInDataLength);//  进行数据填充    for (int i = 0; i < p; i++)    {        plainInChar[plainInDataLength + i] =  p;    }
//3.验证一下填充后的char[]是不是最开始的明文数据    NSLog(@"plainInData=%@",plainInData);    NSData *data = [[NSData alloc]initWithBytes:plainInChar length:sizeof(plainInChar)-p];    NSLog(@"data=%@",data);    NSLog(@"填充后的char[]转成NSString=%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);

可以看到数据是没有问题的

//4.对plainInChar加密,由于源代码中加解密是放在一起的,现在在sm4test.c中新添加两个方法把加密和解密分开,由于计算length总出问题,所以直接把length作为参数传进去void testEncodejiami(unsigned long lenght,unsigned char in[], unsigned char output[]){    unsigned char key[16]   = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10};    sm4_context ctx;    //设置上下文和密钥    sm4_setkey_enc(&ctx,key);    //加密    sm4_crypt_ecb(&ctx,1,lenght,in,output);}void testDecodejiemi(unsigned long lenght, unsigned char in[], unsigned char output[]){    unsigned char key[16]   = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10};    sm4_context ctx;    //设置上下文和密钥    sm4_setkey_dec(&ctx,key);    //解密    sm4_crypt_ecb(&ctx,0,lenght,in,output);}
    //5.调用刚才添加的方法加密    //定义输出密文的变量    unsigned char cipherOutChar[plainInDataLength + p];    testEncodejiami(plainInDataLength + p, plainInChar, cipherOutChar);    //对加密的数据输出    NSData *cipherTextData =  [[NSData alloc]initWithBytes:cipherOutChar length:sizeof(cipherOutChar)];    NSLog(@"密文NSData=%@",cipherTextData);    NSLog(@"密文转成NSString=%@",[[NSString alloc]initWithData:cipherTextData encoding:NSUTF8StringEncoding]);

输出结果是:
这里写图片描述
由于是加密数据,只有解密后才能转成NSString打印,所以第二行打印是null

    //6将cipherTextData作为输入,调用第4步的解密方法,进行解密    //将data拷贝到字符数组中    unsigned char cipherTextChar[cipherTextData.length];    memcpy(cipherTextChar, cipherTextData.bytes, cipherTextData.length);    //调用解密方法,输出是明文plainOutChar    unsigned char plainOutChar[cipherTextData.length];    testDecodejiemi(cipherTextData.length, cipherTextChar, plainOutChar);    //由于明文是填充过的,解密时候要去填充,去填充要在解密后才可以,在解密前是去不了的    int p2 = plainOutChar[sizeof(plainOutChar) - 1];//p2是填充的数据,也是填充的长度    int outLength = cipherTextData.length-p2;//明文的长度    //去掉填充得到明文    unsigned char plainOutWithoutPadding[outLength];    memcpy(plainOutWithoutPadding, plainOutChar, outLength);    //明文转成NSData 再转成NSString打印    NSData *outData = [[NSData alloc]initWithBytes:plainOutWithoutPadding length:sizeof(plainOutWithoutPadding)];    NSString *str =[[NSString alloc]initWithData:outData encoding:NSUTF8StringEncoding];    NSLog(@"解密得到的明文是:%@",str);

这里写图片描述
到此解密成功,源代码已上传http://download.csdn.net/detail/qq_15509071/9607224

0 0
原创粉丝点击