手记

iOS端基于RSA公钥加密和解密

iOS端基于RSA公钥加密和解密

前言

最近在公司项目中被要求使用RSA加密,且要求是全程加解密,期间也是踩了很多的坑,在此做个记录也算给要使用的朋友一点帮助.注意,具体的RSA加密算法内容并不在此文的讨论范围之内.本文更多聚焦于使用部分.

我当前的使用场景和环境:

  • 1.移动端(iOS端)只有公钥,拿不到私钥,私钥后台保留

  • 2.基于base64进行编码

  • 3.全程加密,即和后台通讯的时候请求体是一段base64编码.

  • 4.由于RSA加密机制决定了明文长度不能大于密文长度,所以需要分段加密和解密.

  • 5.使用的密钥是1024位,要和后台统一

首先,如果你着急用,并且需求跟我差不多,我也就不多说了demo链接在下面,直接拿去用就好,如果好用,欢迎star,有问题也请直接提交issue,或者留言,我看到就会回复.

github.com/JVSFlipped/…

直接把JVSRSAHandler文件夹拖进你的工程里面去
可能会有以下直接问题:
1.找不到头文件,请在Build Settings -> SearchPatch -> Header Search Patchs 里面填上对应的文件夹路径
2.库冲突,demo中使用的是openssl,据我所知,支付宝也用了这个东西,它的sdk包含了这个,所以需要删除重复的即可.
3.报错找不到"没有添加.pem密钥文件或者命名不同于代码内名称",这是我在demo中抛出的异常.中使用了rsa_public_key.pem来读取公钥,这个文件可以问后台要,也可以自己生成,这里不展开讲.注意文件的命名必须跟

importRSAKeyWithType:

这个方法中的文件名保持一致.

接下里我详细谈谈我在做这个需求时候踩到的坑和一些注意点:

1.网上有很多公钥加密私钥解密的,我找了很久都没找到合适的公钥解密的解决方案,各位不要去找后台要私钥啊,这牵扯到RSA的加密机制,即使用的策略是非对称加密,即客户端使用公钥,后台使用私钥,公钥加密的内容只有私钥能解开,这样即使客户端的公钥被窃取了(实际上设计是公开公钥的),只要私钥妥善得保管在后台,公钥加密数据的安全就能得到保证.
2.要确定几个重要参数,我在demo中有注释

//RSA算法填充类型,前后台要统一 static NSInteger kRSAPaddingType = RSA_PKCS1_PADDING; //解密长度,前后台要统一   static NSInteger kDecryptionLength = 128; //RSA公钥文件名 static NSString *kPublicKeyFile = @"rsa_public_key"; //加密长度,前后台要统一 static NSInteger kEncryptionLength = 117; //RSA密钥文件名,目前没有此类调用,后续可能会添加 static NSString *kPrivateKeyFile = @"rsa_private_key";

3.有关openssl文件的问题
这是我在使用过程中遇到的问题,当时忘了截图了大概是类似于

architecture x86_64:

的报错,这个主要是因为openssl文件夹中lib下的.a库太老,不支持最新的iOS系统,我提供的demo中应该没有这个问题(因为我弄的是比较新的,具体方法这里不展开讲了,跟这里没啥关系).

4.有关分段加密的问题
由于RSA限制明文长度不能长于密文长度,所以数据过长就需要分段加密,就是说分段加密然后base64编码然后再拼接起来.

获取公钥

不管加密还是解密都需要提前获取公钥

//获取key - (BOOL)importRSAKeyWithType:(KeyType)type {     FILE *file;     NSString *keyName = type == KeyTypePublic?kPublicKeyFile:kPrivateKeyFile;     NSString *keyPath = [[NSBundle mainBundle] pathForResource:keyName ofType:@"pem"];     file = fopen([keyPath UTF8String], "rb");     if (NULL != file)     {         if (type == KeyTypePublic)         {             _rsa = PEM_read_RSA_PUBKEY(file, NULL, NULL, NULL);             assert(_rsa != NULL);         }         else         {             _rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);             assert(_rsa != NULL);         }         fclose(file);         return (_rsa != NULL) ? YES : NO;     }     NSException* exception = [NSException exceptionWithName:@"读取密钥失败!" reason:@"没有添加.pem密钥文件或者命名不同于代码内名称" userInfo:nil];     @throw exception;     return NO; }

加密过程

JVSRSAHandler提供了加密方法将字典转换并基于RSA加密后再base64编码获得字符串的方法

//加密字典 - (NSString *)encryptDictionary:(NSDictionary*)dict WithRSAKeyType:(KeyType)keyType {     //将字典转成json字符串     NSString *jsonString = [self conversionDictionary:dict];     //转成UTF8Data     NSData *UTF8Data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];     //加密过程     NSData *RSAEncryptData = [self encryptionData:UTF8Data WithRSAKeyType:keyType];     //转成base64的string     NSString *encryptString = [RSAEncryptData base64EncodedString];     return encryptString; }

加密方法,这里主要是分段过程:

//加密方法,这里主要是分段内容 - (NSData *)encryptionData:(NSData *)expressData WithRSAKeyType:(KeyType)keyType {     if (expressData && [expressData length]) {         //计划分段加密长度         NSInteger planSubLength = kEncryptionLength;         //数据总长度         NSInteger sumLength = [expressData length];         //分段数         NSInteger blockCount = sumLength/planSubLength + ((sumLength%planSubLength)?1:0);         //总的数据,存放解密后的数据         NSMutableData *sumData = [[NSMutableData alloc ] initWithCapacity:0];         for(int i = 0;i < blockCount; i++)         {             //实际分段长度,注意最后一段不够的问题             int relSubLength = (int)MIN(planSubLength, sumLength - i*planSubLength);             //定义放置待加密数据的数组,容量为实际分段长度(明文,较短)             unsigned char expressArr[relSubLength];             //C函数方法,将数组初始化置空             bzero(expressArr, sizeof(expressArr));             //在expressArr中放入目标要加密的数据             memcpy(expressArr, [[expressData subdataWithRange:NSMakeRange(i*planSubLength, relSubLength)] bytes], relSubLength);             //定义存放加密后数据的数组,因为明文长度不得大于密文长度,所以这里的长度为计划长度(密文,较长)             unsigned char encryptedArr[planSubLength];             //同上,将数组初始化置空             bzero(encryptedArr, sizeof(encryptedArr));             //加密expressArr中的数据并放入encryptedArr数组中             [self encryptFrom:expressArr length:(int)relSubLength to:encryptedArr WithKeyType:keyType];             //取出encryptedArr数组的有效内容长度,不能用数组长度,因为encryptedArr为unsigned char*型,可能有效内容之间也有“\0”,需要去除             int k=0;             //不明白这里为什么是128,按理说128会越界的,因为定义的时候数组长度只有117             for(int j = 0;j< 128;j++)             {                 if(encryptedArr[j] != '\0')                 {                     k = j+1;                 }             }             //同样不明白这里的操作含义,去掉的话加密成功率降低很多             if(k%4 != 0){                                  k = ((int)(k/4) + 1)*4;                              }             //拼接加密后数据             [sumData appendData:[NSData dataWithBytes:encryptedArr length:k]];         }         return sumData;     }     return nil; }

真正的加密部分

//加密部分 - (NSInteger)encryptFrom:(const unsigned char *)expressArr length:(int)length to:(unsigned char *)encryptedArr WithKeyType:(KeyType)keyType {     //导入文件中密钥     if (![self importRSAKeyWithType:keyType])         return 0;     if (expressArr != NULL && encryptedArr != NULL) {         NSInteger status;         switch (keyType) {             case KeyTypePrivate:{                 //私钥加密                 status =  RSA_private_encrypt(length, expressArr,encryptedArr, _rsa, kRSAPaddingType);             }                 break;                              default:{                 //公钥加密                 status =  RSA_public_encrypt(length,expressArr,encryptedArr, _rsa,  kRSAPaddingType);             }                 break;         }         return status;     }     return -1; }

解密过程

JVSHandler提供了解密方法将后台给的base64字符串转化成字典

//解密字符串 - (NSDictionary *)decryptString:(NSString *)encryptedString WithRSAKeyType:(KeyType)keyType {     //将要解密的字符串base64解码     NSData *encryptedData = [[NSData alloc] initWithBase64EncodedString:encryptedString options:NSDataBase64DecodingIgnoreUnknownCharacters];     //解密过程     NSData *jsonData = [self decryptData:encryptedData WithRSAKeyType:keyType];     //将data转成string     NSString *josnString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];     //转成字典输出     NSDictionary *dict = [self dictionaryWithJsonString:josnString];     return dict; }

解密方法,这里主要是分段过程:

//解密方法(主要是分段) - (NSData *)decryptData:(NSData *)encryptedData WithRSAKeyType:(KeyType)keyType {     if (encryptedData && [encryptedData length]) {         //计划解密长度         NSInteger planSubLength = kDecryptionLength;         //数据总长度         NSInteger sumLength = [encryptedData length];         //分段数         NSInteger blockCount = sumLength/planSubLength + ((sumLength%planSubLength)?1:0);         //存放解密后的数据         NSMutableData *sumData = [[NSMutableData alloc ] initWithCapacity:0];         for(int i = 0;i < blockCount; i++)         {             //实际分段的长度,注意最后一段不够的情况             int realSubLength = (int)MIN(planSubLength, sumLength - i*planSubLength);             //定义存放待解密数据的数组encryptedArr(密文,较长)             unsigned char encryptedArr[planSubLength];             //C函数,初始化置空encryptedArr数组             bzero(encryptedArr, sizeof(encryptedArr));             //将待解密的data数据存放入encryptedArr数组中             memcpy(encryptedArr, [[encryptedData subdataWithRange:NSMakeRange(i*planSubLength, realSubLength)] bytes], realSubLength);             //定义存放解密出来的数据的数组expressArr(明文,较短)             unsigned char expressArr[realSubLength];             //初始化置空expressArr数组             bzero(expressArr, sizeof(expressArr));             //解密encryptedArr中的数据并存入expressArr中             [self decryptFrom:encryptedArr length:realSubLength to:expressArr WithKeyType:keyType];             int k=0;             //取出expressArr数组的有效内容长度,不能用数组长度,因为expressArr为unsigned char*型,可能有效内容之间也有“\0”,需要去除             for(int j = 0;j< planSubLength;j++)             {                 if(expressArr[j] != '\0')                 {                     k = j+1;                 }             }             //拼接解密出来的数据             [sumData appendData:[NSData dataWithBytes:expressArr length:k]];         }         return sumData;     }     return nil; }

真正的解密部分

//解密方法 - (NSInteger)decryptFrom:(const unsigned char *)encryptedArr length:(int)length to:(unsigned char *)expressArr WithKeyType:(KeyType)keyType {     //获取密钥     if (![self importRSAKeyWithType:keyType])         return -1;     if (encryptedArr != NULL && expressArr != NULL) {         int status;         switch (keyType) {             case KeyTypePrivate:{                 //私钥解密                 status =  (int)RSA_private_decrypt(length, encryptedArr,expressArr, _rsa, kRSAPaddingType);             }                 break;             default:{                 //公钥解密                 status =  RSA_public_decrypt(length, encryptedArr, expressArr, _rsa,  kRSAPaddingType);             }                 break;         }         return status;     }     return -1; }

其他的字符串转字典互转的部分我就不详细码出来了,demo里面都有,注意留意控制台打印的信息,会有测试加密出来的密文保存的txt文档路径以及解密密文出来的字典打印.

总结

简单来说,在这个过程中JVSHandler做了以下事情:

加密过程:字典 -> 字符串(UTF-8) -> Data(UTF-8) -> Data(分段) -> Data(加密) -> 字符串(base64)

解密过程:字符串(base64) -> Data(base64解码) -> Data(分段) -> Data(解密) -> 字符串(UTF-8) -> 字典

目前存在的问题

  • 1.0版本的时候加密不稳定,后来新版本添加了代码基本解决了这个问题,但是这部分代码我是在网上找来的,具体为啥我目前也很懵(emmmmmmm...)

//不明白这里为什么是128,按理说128会越界的,因为定义的时候数组长度只有117             for(int j = 0;j< 128;j++)             {                 if(encryptedArr[j] != '\0')                 {                     k = j+1;                 }             }             //同样不明白这里的操作含义,去掉的话加密成功率降低很多             if(k%4 != 0){                 k = ((int)(k/4) + 1)*4;             }

我的疑惑点在哪我已经写在上面了,希望有大神可以赐教.我目前怀疑是RSA本身需要或是编码规则需要,目前还没时间仔细去研究,后续如果搞明白了我会补充的.具体的RSA加解密算法过程之后有时间也会研究下,有必要也会做出一份这样的记录文档分享出来.


0人推荐
随时随地看视频
慕课网APP