IOS基础:数据安全
原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、简介
- 1、需求
- 2、加密方案选择
- 3、建议的加密方案
- 二、哈希(散列)函数
- 1、简介
- 2、MD5加密
- 三、AES+Base64
- 1、对称加密算法
- 2、Base64编码
- 3、Demo演示
- 四、非对称加密算法 ——(RSA)现代加密算法
- 五、使用RNCryptor框架
- 1、导入框架
- 2、基本使用
- 3、CXYRNCryptorTool mac端加密小工具
- 4、PDF加解密
- Demo
- 参考文献
一、简介
1、需求
在开发应用的时候,数据的安全性至关重要。我们会要求开发者不允许明文传输用户的隐私数据,同时也不允许明文保存用户的隐私数据;这就需要我们去对敏感的数据进行加密处理。
对于iOS持久化方面,无论使用userDefault
、plist
文件、归档、数据库等存储方式,都需要考虑一个问题,如何安全的存储数据?对于这个问题,可以对数据加密保存。
2、加密方案选择
a、常见的加密算法
-
哈希(散列)函数:
MD5
、SHA
-
对称加密算法:
DES
、3DES
、AES
-
非对称加密算法:
RSA
b、选择加密算法
- 一些摘要算法或编码处理。这只是一种把明文转为非明文的一种方式。摘要算法不可逆,但数据无法还原,不符合需求。编码处理,可以反编码还原明文,实际上数据并没有变得更安全,也是不符合需求。但采用编码的方式,还是比直接使用明文要好那么一点点。
- 使用特定的秘钥执行某种对称加密。使用公开的对称算法进行加解密,只要保证秘钥的安全,是可行的。
- 使用特定的公钥私钥执行某种非对称加密。相对于对称加密,非对称加密效率较低,但安全系数更高。
- 某种自定义加密算法。破解难度因算法本身设计有关,但这个一般都是比较有实力的公司才这么做。
面临了一个秘钥安全的问题,一般来说,我们可能会把秘钥硬编码到代码中,但我们的二进制文件存在一个字符串常量区,只要这个字符串在代码中被引用了,那么编译器就会把其写进字符串常量区。如果这样,稍微的分析下二进制文件,就可能把秘钥获取。所以秘钥硬编码是比较危险的。面对这个问题,可以通过把秘钥分拆成多个字符串,再通过某些处理生成真正的秘钥,来提高安全性。
从安全范围的角度去考虑,如果在每一个客户端都是同样的加密算法,同样的秘钥,那么只要其中一个被破解,整个大厦立马倒塌。为此,可以给安卓和苹果分别分配不同的秘钥,这样就能把两个平台,这块的安全性隔离。既然有了这样的想法,那么前进一步,如果每个客户端都拥有不同的秘钥,并且是一种随机秘钥(随机安全性更高,当然可能伪随机,虽然伪随机实际上是通过算法来生成的,有规律,但其破解难度很大),那么就能把这块的安全性做到客户端隔离。
3、建议的加密方案
方案
- 使用AES对称加密算法
- 使用随机算法生成本地秘钥,秘钥中添加时效性信息,并保存到
keyChain
中
优点
- 客户端隔离
- 对称加解密效率高
- 随机秘钥破解难度大
缺点:由于客户端隔离,秘钥都不一样,数据无法统一分析
二、哈希(散列)函数
1、简介
a、哈希的用途
哈希主要用于验证,就像人的指纹一样。涉及到验证的部分一般都可以使用Md5
。如:向百度云上传文件,百度云对每个文件都进行MD5
加密,将你上传文件的MD5
与已有的进行比对,假如已经存在则说明你上传的文件在库里已经有了,直接秒传。一般来说相同的文件库里面只会保存一份。
b、哈希破解(散列碰撞)
不同的数据MD5
加密后得到的应该是不同的32位字符,但是32位字符排列组合的个数是有限的,但是数据是无限的,比如自然数字。对无限的数据MD5
加密得到有限个32位字符,其中肯定是有重复的,即不同的数据哈希之后可能得到相同的结果。
2、MD5 散列函数
a、特点
- 对输入信息生成唯一的128位散列值(32个字符)。所有的数据(视频、音频、文件、只要存在于硬盘或内存中的)都是可以被
MD5
加密的,得到的都是32个字符。 -
MD5
加密后的结果是不可逆的 - 同一个数据加密之后得到的结果是一样的
b、NSString+Hash文件中MD5算法
// 引入CC_MD5
#import <CommonCrypto/CommonCrypto.h>
/** 计算MD5散列结果
* @return 32个字符的MD5散列字符串
*/
- (NSString *)md5String;
// 计算MD5散列结果
- (NSString *)md5String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
/** 返回二进制 Bytes 流的字符串表示形式
* @param bytes 二进制 Bytes 数组
* @param length 数组长度
* @return 字符串表示形式
*/
- (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length
{
NSMutableString *strM = [NSMutableString string];
for (int i = 0; i < length; I++)
{
[strM appendFormat:@"%02x", bytes[I]];
}
return [strM copy];
}
c、运行结果
调用MD5
算法:
- (void)viewDidLoad
{
[super viewDidLoad];
[self MD5Demo];
}
- (void)MD5Demo
{
NSString *originString = @"123";
NSString *MD5String = [originString md5String];
NSLog(@"原字符串为:%@",originString);
NSLog(@"MD5加密后的字符为:%@",MD5String);
}
输出结果为:
2020-09-09 15:19:47.719566+0800 EncryptedDemo[60660:4894670] 原字符串为:123
2020-09-09 15:19:47.719683+0800 EncryptedDemo[60660:4894670] MD5加密后的字符为:202cb962ac59075b964b07152d234b70
终端测试命令:
xiejiapei@xiejiapeis-iMac ~ % md5 -s "string"
MD5 ("string") = b45cffe084dd3d20d928bee85e7b0f21
d、破解
MD5解密网站 针对md5
、sha1
等全球通用公开的加密算法进行反向查询,通过穷举字符组合的方式,创建了明文密文对应查询数据库,创建的记录约90万亿条,占用硬盘超过500TB,查询成功率95%以上,所以这种方式是不安全的,相对来说比较容易破解。
随着 MD5
碰撞生成器的出现,MD5
算法不应被用于任何软件完整性检查或代码签名的用途。苹果已经在iOS 13.0弃用MD5
。
// CC_MD5已弃用:在iOS 13.0中首次被弃用-此函数在密码上已被破坏,不应在安全上下文中使用。客户端应迁移到SHA256(或更高版本)
'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).
e、加盐(Salt)
传统增加安全性的方法是加盐(Salt
),在明文的固定位置插入位数足够多、足够复杂的随机串,然后再进行MD5
。
- (void)MD5Demo
{
NSString *originString = @"123";
NSString *MD5String = [originString md5String];
NSLog(@"原字符串为:%@",originString);
NSLog(@"MD5加密后的字符为:%@",MD5String);
// 加盐(Salt)
NSString *salt = @"fdsfjf)*&*JRhuhew7HUH^&udn&&86&*";
NSString *saltString = [originString stringByAppendingString:salt];
NSString *MD5SaltString = [saltString md5String];
NSLog(@"加盐(Salt)+ MD5加密后的字符为:%@",MD5SaltString);
}
输出结果为:
2020-09-09 15:39:41.667676+0800 EncryptedDemo[60865:4908204] 原字符串为:123
2020-09-09 15:39:41.667784+0800 EncryptedDemo[60865:4908204] MD5加密后的字符为:202cb962ac59075b964b07152d234b70
2020-09-09 15:39:41.667868+0800 EncryptedDemo[60865:4908204] 加盐(Salt)+ MD5加密后的字符为:3d886c749350df1977fcd2dcb237f8f9
缺点是盐是固定不变的,一旦泄露后果不堪设想。使用加盐通过MD5
解密之后,很容易发现规律从而破解。
f、HMAC 散列
HMAC
是个使用一个密钥加密并且两次的散列!
❶ 以用户账号登录注册为例
注册:
- 客户端向服务器发起注册请求,这时服务器生成一个密钥
key
(并保存),然后将密钥返回给客户端。 - 客户端根据密钥对密码进行
HMAC
加密生成密文,传给服务器,服务器保存(不允许明文保存用户的隐私信息)。 - 服务器返回注册成功后,客户端将密钥保存到手机钥匙串(减少密钥传递次数,提高安全性)。
登录:
- 客户端读取手机钥匙串中的密钥,对密码进行
HMAC
加密后,向服务器发出登录请求。 - 服务器根据账号读取数据库中的密文与客户端提交的进行比较。
❷ 算法
/** 计算HMAC MD5散列结果
* @return 32个字符的HMAC MD5散列字符串
*/
- (NSString *)hmacMD5StringWithKey:(NSString *)key;
// HMAC + MD5
- (NSString *)hmacMD5StringWithKey:(NSString *)key
{
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
进行调用:
// HMAC + MD5
NSString *HMACMD5String = [originString hmacMD5StringWithKey:@"xiejiapei"];
NSLog(@"HMAC + MD5 加密后的字符为:%@",HMACMD5String);
输出结果为:
2020-09-09 16:01:34.220311+0800 EncryptedDemo[61099:4922578] HMAC + MD5 加密后的字符为:785c229bef809fb9eab3d2e789f82716
破解网站
终端测试命令:
xiejiapei@xiejiapeis-iMac ~ % echo -n "string" | openssl dgst -md5 -hmac "key"
38fc25be306abaee29caa95a980cd645
❸ 实际应用
可以利用这个做出类型于设备锁或QQ手机令牌的功能,目的就是为了在他人设备上无法登录自己的账号。
这里说下大致的流程,在客户端发出登录请求时,在手机钥匙串读取密钥,当这个密钥不存在的时,就说明是在他人设备上操作的,这时可以利用推送通知或短信告诉自己,是否允许他人设备登录,只允许此次登录还是授权永久登录。如果仅登陆一次则不保存密钥到钥匙串,下次登录重复以上操作,如果授权永久登录可以再他人设备保存密钥。
g、HMAC + MD5 + 添加时间戳
只是HMAC + MD5
仍然存在不安全的成分。如果在上面的第2步中,拦截传给服务器的post
内容获取加密后的密文,再模拟客户端向服务器发出请求,就黑掉了!所以为了再次提高安全性,需要添加时间戳。
第一步
客户端将HMAC
得到的数据拼接当前服务器时间,再用MD5
加密。即(HMAC+202009101633).md5String
,将加密后的数据post
到服务器。202009101633
须为当前服务器时间,这样可以避免客户端时间不统一。
第二步
服务器开始验证。根据账号在数据库中读取HMAC
,将当前服务器时间拼接后,再用MD5
加密,得到32位字符与客户端提交的32位字符进行比较。
这里会有两种情况:
情况一:服务器拼接的时间与客户端拼接的时间一致,得到相同的密文,则通过验证。
情况二:服务器拼接的时间与客户端拼接的时间不一致,得到的密文不相同,这时候服务器就拼接上一个时间。
客服端密文是:(HMAC+202009101633).md5String
服务器第一次拼接时间的密文是:(HMAC+202009101634).md5String
。
两者不一致,服务器第二次拼接上一个时间段密文:(HMAC+202009101633).md5String
,一致则通过。
进行这样的操作就是让客户端发出的请求的时间有效性只维持1分钟,如果还觉得不安全可以在拼接时间的时候添加秒,进一步缩短时间的有效性,以提高安全性。
g、文件的MD5散列
// 终端测试命令
md5 file.dat
/** 计算文件的MD5散列结果
* @return 32个字符的MD5散列字符串
*/
- (NSString *)fileMD5Hash;
#define FileHashDefaultChunkSizeForReadingData 4096
- (NSString *)fileMD5Hash
{
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil)
{
return nil;
}
CC_MD5_CTX hashCtx;
CC_MD5_Init(&hashCtx);
while (YES)
{
@autoreleasepool
{
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_MD5_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0)
{
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
3、SHA 散列函数
a、简介
b、SHA 散列算法
❶ SHA1散列
// 终端测试命令
echo -n "string" | openssl sha -sha1
/** 计算SHA1散列结果
* @return 40个字符的SHA1散列字符串
*/
- (NSString *)sha1String;
// 计算SHA1散列结果
- (NSString *)sha1String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
❷ SHA224散列
// 终端测试命令
echo -n "string" | openssl sha -sha224
/** 计算SHA224散列结果
* @return 56个字符的SHA224散列字符串
*/
- (NSString *)sha224String;
// 计算SHA224散列结果
- (NSString *)sha224String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA224_DIGEST_LENGTH];
CC_SHA224(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA224_DIGEST_LENGTH];
}
❸ SHA256散列
// 终端测试命令
echo -n "string" | openssl sha -sha256
/** 计算SHA256散列结果
* @return 64个字符的SHA256散列字符串
*/
- (NSString *)sha256String;
// 计算SHA256散列结果
- (NSString *)sha256String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
❹ SHA256散列
// 终端测试命令
echo -n "string" | openssl sha -sha384
/** 计算SHA 384散列结果
* @return 96个字符的SHA 384散列字符串
*/
- (NSString *)sha384String;
// 计算SHA384散列结果
- (NSString *)sha384String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA384_DIGEST_LENGTH];
CC_SHA384(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA384_DIGEST_LENGTH];
}
❺ SHA256散列
// 终端测试命令
echo -n "string" | openssl sha -sha512
/** 计算SHA 512散列结果
* @return 128个字符的SHA 512散列字符串
*/
- (NSString *)sha512String;
// 计算SHA 512散列结果
- (NSString *)sha512String
{
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CC_SHA512(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
c、HMAC + SHA 散列算法
❶ HMAC SHA1散列
// 终端测试命令
echo -n "string" | openssl sha -sha1 -hmac "key"
/** 计算HMAC SHA1散列结果
* @return 40个字符的HMAC SHA1散列字符串
*/
- (NSString *)hmacSHA1StringWithKey:(NSString *)key;
// 计算HMAC SHA1散列结果
- (NSString *)hmacSHA1StringWithKey:(NSString *)key
{
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
❷ HMAC SHA256散列
// 终端测试命令
echo -n "string" | openssl sha -sha512 -hmac "key"
/** 计算HMAC SHA256散列结果
* @return 64个字符的HMAC SHA256散列字符串
*/
- (NSString *)hmacSHA256StringWithKey:(NSString *)key;
// 计算HMAC SHA256散列结果
- (NSString *)hmacSHA256StringWithKey:(NSString *)key
{
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
❸ HMAC SHA512散列
// 终端测试命令
echo -n "string" | openssl sha -sha512 -hmac "key"
/** 计算HMAC SHA512散列结果
* @return 128个字符的HMAC SHA512散列字符串
*/
- (NSString *)hmacSHA512StringWithKey:(NSString *)key;
// 计算HMAC SHA512散列结果
- (NSString *)hmacSHA512StringWithKey:(NSString *)key
{
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
d、文件的SHA散列结果
❶ 文件的SHA1散列
// 终端测试命令
openssl sha -sha1 file.dat
/** 计算文件的SHA1散列结果
* @return 40个字符的SHA1散列字符串
*/
- (NSString *)fileSHA1Hash;
// 计算文件的SHA1散列结果
- (NSString *)fileSHA1Hash
{
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil)
{
return nil;
}
CC_SHA1_CTX hashCtx;
CC_SHA1_Init(&hashCtx);
while (YES)
{
@autoreleasepool
{
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA1_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0)
{
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
❷ 文件的SHA256散列
// 终端测试命令
openssl sha -sha256 file.dat
/** 计算文件的SHA256散列结果
* @return 64个字符的SHA256散列字符串
*/
- (NSString *)fileSHA256Hash;
// 计算文件的SHA256散列结果
- (NSString *)fileSHA256Hash
{
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil)
{
return nil;
}
CC_SHA256_CTX hashCtx;
CC_SHA256_Init(&hashCtx);
while (YES)
{
@autoreleasepool
{
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA256_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0)
{
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
❸ 文件的SHA512散列
// 终端测试命令
openssl sha -sha512 file.dat
/** 计算文件的SHA512散列结果
* @return 128个字符的SHA512散列字符串
*/
- (NSString *)fileSHA512Hash;
// 计算文件的SHA512散列结果
- (NSString *)fileSHA512Hash
{
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil)
{
return nil;
}
CC_SHA512_CTX hashCtx;
CC_SHA512_Init(&hashCtx);
while (YES)
{
@autoreleasepool
{
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA512_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0)
{
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CC_SHA512_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
三、AES+Base64
1、对称加密算法
a、概念
对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。
数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。
明文——>加密——>密文
密文——>解密——>明文
b、特点
- 算法公开
- 计算量小
- 加密速度快
- 加密效率高
c、缺点
- 密钥管理困难
- 使用成本较高
- 双方都使用同样的密钥
- 安全性得不到保证
2、Base64编码
a、作用
为什么会有Base64
编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。最好的方法就是在不改变传统协议的情 况下,做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示,问题就解决了。Base64
编码应运而生,Base64
就是一种基于64个可打印字符来表示二进制数据的表示方法。
IOS中有两种Base64
编码方式,一种是NSData
类中自带的编码方法。一种是Google的GTMBase64
,可以用CocoaPods
导入。
b、索引表
看一下Base64
的索引表,字符选用了"A-Z、a-z、0-9、+、/" 64
个可打印字符。数值代表字符的索引,这个是标准Base64
协议规定的,不能更改。
数值 | 字符 | 数值 | 字符 | 数值 | 字符 | 数值 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
c、原理
Base64
的码表只有64个字符, 如果要表达64个字符的话,使用6的bit
即可完全表示(2的6次方为64)。因为Base64
的编码只有6个bit
即可表示,而正常的字符是使用8个bit
表示, 8和6的最小公倍数是24,所以4个Base64
字符可以表示3个标准的ascll
字符。
如果是字符串转换为Base64
码, 会先把对应的字符串转换为ascll
码表对应的数字, 然后再把数字转换为2进制, 比如a
的ascll
码为97, 97的二进制是:01100001
, 把8个二进制提取成6个,剩下的2个二进制和后面的二进制继续拼接, 最后再把6个二进制码转换为Base64
对应的编码。当转换到最后, 最后的字符不足的话,直接在最后添加=
号即可。
字符串 | a | b | c | |
---|---|---|---|---|
ASCII | 97 | 98 | 99 | |
8bit | 01100001 | 01100010 | 01100011 | |
6bit | 011000 | 010110 | 001001 | 100011 |
十进制 | 24 | 22 | 9 | 35 |
对应编码 | Y | W | J | j |
d、特点
说起Base64
编码可能有些奇怪,因为大多数的编码都是由字符转化成二进制的过程,而从二进制转成字符的过程称为解码。而Base64
的概念就恰好反了,由二进制转到字符称为编码,由字符到二进制称为解码。
Base64
编码主要用在传输、存储、表示二进制等领域,还可以用来加密,但是不能叫做加密算法,因为这种加密比较简单,其算法和充当密钥的索引表都是公开的,只是一眼看上去不知道什么内容罢了。
Base64
编码是从二进制到字符的过程,像一些中文字符用不同的编码转为二进制时,产生的二进制是不一样的,所以最终产生的Base64
字符也不一样。例如"上网"对应utf-8
格式的Base64
编码是"5LiK572R"
, 对应GB2312
格式的Base64
编码是"yc/N+A=="
。
3、Demo演示
准备工作
❶ 导入辅助文件
-
SAMKeychain
文件夹 -
GTMBase64
文件夹
❷ 封装成工具类
考虑到加密对象和使用场景,理所当然的将加密过程放到了NSString
的类别中,即下面说到的NSString+AES
。
❸ 与后端接口保持一致
-
AES
有多种加密模式:ECB
、CBC
、CFB
、OFB
。至于用哪个看你心情了,但是要同WebService
同学保持一致。 - 加密解密过程需要你提供一个
Key
,一定是和WebService
同学约定好的,不然是解密不了的。 - 偏移量(
IV
)是非必须的,不过如果想加的话也需要和后端保持一致。 - 补码方式:
PKCS7Padding
、PKCS5Padding
a、运行效果
2020-09-08 16:49:00.395366+0800 LocalEncryptDemo[45516:4298448] LocalEncryptService:秘钥重新生成
2020-09-08 16:49:00.395472+0800 LocalEncryptDemo[45516:4298448] 秘钥为:0174096234160359
2020-09-08 16:49:00.395610+0800 LocalEncryptDemo[45516:4298448] aes128加密后:{length = 16, bytes = 0x7282e38be6c799e28cacec11973c6413}
2020-09-08 16:49:00.395718+0800 LocalEncryptDemo[45516:4298448] base64编码后:coLji+bHmeKMrOwRlzxkEw==
2020-09-08 16:49:27.772725+0800 LocalEncryptDemo[45516:4298448] base64解密后:{length = 16, bytes = 0x7282e38be6c799e28cacec11973c6413}
2020-09-08 16:49:27.773796+0800 LocalEncryptDemo[45516:4298448] 加密秘钥 33 秒后过期
2020-09-08 16:49:27.773938+0800 LocalEncryptDemo[45516:4298448] 秘钥为:0174096234160359
2020-09-08 16:49:27.774069+0800 LocalEncryptDemo[45516:4298448] aes128解密后的:Hello World
2020-09-08 16:50:17.868817+0800 LocalEncryptDemo[45516:4298448] 点击了清空按钮
2020-09-08 16:50:47.524991+0800 LocalEncryptDemo[45516:4298448] 加密秘钥已过期 97 秒
2020-09-08 16:50:47.527602+0800 LocalEncryptDemo[45516:4298448] LocalEncryptService:秘钥重新生成
2020-09-08 16:50:47.527713+0800 LocalEncryptDemo[45516:4298448] 秘钥为:00396481442BD97A
2020-09-08 16:50:47.527805+0800 LocalEncryptDemo[45516:4298448] aes128加密后:{length = 16, bytes = 0xdbdf7727507e8191e4f2794480ac40d4}
2020-09-08 16:50:47.527906+0800 LocalEncryptDemo[45516:4298448] base64编码后:2993J1B+gZHk8nlEgKxA1A==
2020-09-08 16:50:57.423980+0800 LocalEncryptDemo[45516:4298448] base64解密后:{length = 16, bytes = 0xdbdf7727507e8191e4f2794480ac40d4}
2020-09-08 16:50:57.425182+0800 LocalEncryptDemo[45516:4298448] 秘钥为:00396481442BD97A
2020-09-08 16:50:57.425292+0800 LocalEncryptDemo[45516:4298448] aes128解密后的:97 Boy
运行效果
b、LocalEncryptViewController
点击加密按钮
- (void)encodeAction
{
// sku为空
if (self.skuTextField.text.length == 0)
{
[self showInfo:@"请输入sku" title:@"提示"];
return;
}
// 加密内容为空
if (self.encryptionTextView.text.length == 0)
{
[self showInfo:@"请输入加密内容" title:@"提示"];
return;
}
// 有效期存在
if (self.validitySecondsTextField.text)
{
// 绑定标识和有效期
[[LocalEncryptService shareInstance] bindSku:self.skuTextField.text validityPeriod:@(self.validitySecondsTextField.text.integerValue)];
}
// 进行加密并保存
NSString *encodeString = [[LocalEncryptService shareInstance] encryptAndSaveInfo:self.encryptionTextView.text SkuNum:self.skuTextField.text];
// 展示加密后的内容
NSString *encodeInfo = [NSString stringWithFormat:@"\n\n加密后的内容\n%@",encodeString];
self.encryptionTextView.text = [self.encryptionTextView.text stringByAppendingString:encodeInfo];
// 倒计时
[LocalEncryptViewController dispatchSourceTimerWithSeconds:self.validitySecondsTextField.text.integerValue inProgressBlock:^(NSInteger sec) {
if (sec == 0)
{
self.timeLabel.text = @"加密秘钥已过期";
return ;
}
self.timeLabel.text = [NSString stringWithFormat:@"加密秘钥%@s秒后过期", @(sec)];
} periodOverBlock:^{
}];
}
点击解密按钮
- (void)decodeAction
{
// sku为空
if (self.skuTextField.text.length == 0)
{
[self showInfo:@"请输入sku" title:@"提示"];
return;
}
// 进行解密并获取
NSError *error;
NSString *decodeString = [[LocalEncryptService shareInstance] decryptAndQueryWithSkuNum:self.skuTextField.text error:&error];
if (error) //解密出错
{
[self showInfo:error.domain title:@"提示"];
}
else
{
// 展示解密后的内容
NSString *decodeInfo = [NSString stringWithFormat:@"解密后的内容\n%@",decodeString];
self.decryptionTextView.text = decodeInfo;
}
}
点击关闭键盘和清空内容按钮
// 关闭键盘
- (void)endEditing
{
[self.view endEditing:YES];
}
// 清空内容
- (void)clearTextField
{
NSLog(@"点击了清空按钮");
self.encryptionTextView.text = nil;
self.decryptionTextView.text = nil;
_timeLabel.text = nil;
}
显示提示信息
- (void)showInfo:(NSString *)info title:(NSString *)title
{
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:title message:info preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
}];
[alertVC addAction:alertAction];
[self presentViewController:alertVC animated:YES completion:nil];
}
用于计算加密秘钥失效的倒计时
+ (void)dispatchSourceTimerWithSeconds:(NSInteger)seconds inProgressBlock:(void (^)(NSInteger))inProgressBlock periodOverBlock:(void (^)(void))periodOverBlock
{
NSInteger timeOut = seconds; //default:60
__block NSInteger second = timeOut;
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, quene);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (second == 0)
{
if(periodOverBlock)
{
periodOverBlock();
}
second = timeOut;
dispatch_cancel(timer);
}
else
{
second--;
if(inProgressBlock)
{
inProgressBlock(second);
}
}
});
});
dispatch_resume(timer);
}
c、LocalEncryptService
LocalEncryptService.h
@interface LocalEncryptService : NSObject
/** 单例 */
+ (instancetype)shareInstance;
/** 绑定标识和有效期 */
- (void)bindSku:(NSString *)sku validityPeriod:(NSNumber *)seconds;
/** 强制清除加密秘钥 */
- (void)forceClearPriEncryptKeyWithSkuNum:(NSString *)sku;
/** 加密并保存 */
- (NSString *)encryptAndSaveInfo:(NSString *)info SkuNum:(NSString *)sku;
/** 解密并获取 */
- (NSString *)decryptAndQueryWithSkuNum:(NSString *)sku error:(NSError **)error;
@end
LocalEncryptService.m
#import "LocalEncryptService.h"
#import "SAMKeychain.h"
#import "NSData+AES.h"
// 添加到sku前面的前缀
static NSString *AESPriPerfix = @"AES";
@interface LocalEncryptService ()
// 绑定标识和有效期
@property (nonatomic, strong) NSMutableDictionary *validityPeriods;
@end
@implementation LocalEncryptService
.......
@end
接口方法的实现
// 单例
+ (instancetype)shareInstance
{
static dispatch_once_t onceToken;
static LocalEncryptService *service;
dispatch_once(&onceToken, ^{
service = LocalEncryptService.new;
service.validityPeriods = @{}.mutableCopy;
});
return service;
}
// 绑定标识和有效期
- (void)bindSku:(NSString *)sku validityPeriod:(NSNumber *)seconds
{
if ([sku isKindOfClass:NSString.class] && [seconds isKindOfClass:NSNumber.class] && seconds.integerValue != 0)
{
[self.validityPeriods setValue:seconds forKey:sku];
}
}
// 强制清除加密秘钥
- (void)forceClearPriEncryptKeyWithSkuNum:(NSString *)sku
{
[SAMKeychain deletePasswordForService:sku account:sku];
}
// 加密并保存
- (NSString *)encryptAndSaveInfo:(NSString *)info SkuNum:(NSString *)sku;
{
// 加密
NSString *encryptInfo = [self encodeWithString:info sku:sku];
if (encryptInfo)
{
// 保存
[[NSUserDefaults standardUserDefaults] setObject:encryptInfo forKey:[AESPriPerfix stringByAppendingString:sku]];
}
return encryptInfo;
}
// 解密并获取
- (NSString *)decryptAndQueryWithSkuNum:(NSString *)sku error:(NSError **)error;
{
// 获取
NSString *decryptInfo = [[NSUserDefaults standardUserDefaults] objectForKey:[AESPriPerfix stringByAppendingString:sku]];
if (decryptInfo)
{
// 解密
return [self decodeWithString:decryptInfo sku:sku error:error];
}
return nil;
}
加密算法
- (NSString*)encodeWithString:(NSString *)string sku:(NSString *)sku
{
// 1.将内容转化为数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// 2.获取秘钥
NSString *key = [self codeKeyWithSuk:sku error:nil];
// 3.根据秘钥对内容进行aes加密
NSData *aesData = [data AES128EncryptWithKey:key];
NSLog(@"aes128加密后:%@",aesData);
// 4.再进行base64编码
NSString *base64Data = [NSData encodeBase64Data:aesData];
NSLog(@"base64编码后:%@",base64Data);
return base64Data;
}
解密算法
- (NSString*)decodeWithString:(NSString *)string sku:(NSString *)sku error:(NSError **)error;
{
// 1.解开base64编码
NSData *decodeBase64Data = [NSData decodeBase64String:string];
NSLog(@"base64解密后:%@",decodeBase64Data);
// 2.获取秘钥
NSError *keyError;
NSString *key = [self codeKeyWithSuk:sku error:&keyError];
if(keyError)
{
if (error != NULL)
{
*error = [[NSError alloc] initWithDomain:keyError.domain code:keyError.code userInfo:keyError.userInfo];
}
return nil;
}
// 3.根据秘钥解开aes加密内容
NSData *decryptAesData = [decodeBase64Data AES128DecryptWithKey:key];
// 4.将数据转化为字符串
NSString *resultString = [[NSString alloc] initWithData:decryptAesData encoding:NSUTF8StringEncoding];
NSLog(@"aes128解密后的:%@",resultString);
return resultString;
}
获取秘钥算法
- (NSString*)codeKeyWithSuk:(NSString *)sku error:(NSError **)error;
{
// 1.根据绑定标识创建秘钥
NSString *key = [SAMKeychain passwordForService:sku account:sku];
// 2.根据绑定标识获取有效期
NSInteger validityPeriod = [[self.validityPeriods valueForKey:sku] integerValue];
// 3.最近生成的本地秘钥
if (validityPeriod && key.length >= 10)
{
// 取以秒为单位时间作比较
NSString *keyTime = [key substringWithRange:NSMakeRange(0, 10)];
long long last = keyTime.longLongValue;
long long now = (long long)[NSDate date].timeIntervalSince1970;
if (now - last > validityPeriod || last > now) //秘钥失效
{
key = nil;
NSLog(@"加密秘钥已过期 %lld 秒",now - last - validityPeriod);
if (error != NULL)
{
*error = [[NSError alloc] initWithDomain:@"加密秘钥过期" code:-1 userInfo:nil];
}
}
else// 尚未失效
{
NSLog(@"加密秘钥 %lld 秒后过期",validityPeriod - (now - last));
}
}
// 4.key不存在
// 首次随机生成用于信息加密KEY,确保不同手机的加密key是不同的
// 可以凭借时间戳设定key的时效性
if (!key.length)
{
// 4.1 当前时间
NSString *time = [NSString stringWithFormat:@"%lld",(long long)[NSDate date].timeIntervalSince1970 * 1000];
// 4.2 随机生成用于信息加密KEY
key = [time stringByAppendingString:[NSString stringWithFormat:@"%ld",(long)arc4random()]];
// 4.3 添加salt
NSString *salt = nil;
if ([NSUUID UUID].UUIDString.length >= 2)
{
salt = [[NSUUID UUID].UUIDString substringWithRange:NSMakeRange(0, 5)];
}
else
{
salt = @"xjnkk";
}
key = [key stringByAppendingString:salt];
// 4.4 存储加密秘钥
[SAMKeychain setPassword:key forService:sku account:sku];
NSLog(@"LocalEncryptService:秘钥重新生成");
}
// 5. 16字节128位秘钥
if (key.length >= 16)
{
// aes128
key = [key substringWithRange:NSMakeRange(key.length - 16, 16)];
NSLog(@"秘钥为:%@",key);
}
return key;
}
d、NSData+AES
NSData+AES.h
@interface NSData(LE)
/** base64编码 */
+ (NSString*)encodeBase64Data:(NSData *)data;
/** base64解码 */
+ (NSData*)decodeBase64String:(NSString * )input;
/** 加密 */
- (NSData *)AES128EncryptWithKey:(NSString *)key;
/** 解密 */
- (NSData *)AES128DecryptWithKey:(NSString *)key;
@end
NSData+AES.m
#import "NSData+AES.h"
#import "GTMBase64.h"
#import <CommonCrypto/CommonCryptor.h> //使用kCCKeySizeAES128
@implementation NSData(LE)
.......
@end
base64编码和解码
// base64编码
+ (NSString*)encodeBase64Data:(NSData *)data
{
// 进行base64编码
data = [GTMBase64 encodeData:data];
// 转为字符串
NSString *base64String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return base64String;
}
// base64解码
+ (NSData*)decodeBase64String:(NSString * )input
{
// 转为数据
// usedLossyConversion:是否使用有损转化
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
// 进行base64解码
data = [GTMBase64 decodeData:data];
return data;
}
AES加密
- (NSData *)AES128EncryptWithKey:(NSString *)key
{
char keyPtr[kCCKeySizeAES128 + 1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
// keyPtr加解密的密钥
// 加密模式kCCOptionECBMode
// 补码方式kCCOptionPKCS7Padding
// iv向量,此属性可选,但只能用于CBC模式
// kCCEncrypt: 编码 or 解码
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyPtr,
kCCBlockSizeAES128,
NULL /* initialization vector (optional) */,
[self bytes],
dataLength, /* input */
buffer,
bufferSize, /* output */
&numBytesEncrypted);
if (cryptStatus == kCCSuccess)
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free(buffer); //free the buffer;
return nil;
}
AES解密
- (NSData *)AES128DecryptWithKey:(NSString *)key
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyPtr,
kCCBlockSizeAES128,
NULL /* initialization vector (optional) */,
[self bytes],
dataLength, /* input */
buffer,
bufferSize, /* output */
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil;
}
四、非对称加密算法 ——(RSA)现代加密算法
1、简介
a、概念
非对称加密算法需要两个密钥:公开密钥(publickey
)和私有密钥(privatekey
)。
公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
b、流程
-
A
要向B
发送信息,A
和B
都要产生一对用于加密和解密的公钥和私钥 -
A
的私钥保密,A
的公钥告诉B
。B
的私钥保密,B
的公钥告诉A
。 -
A
要给B
发送信息时,因为A
知道B
的公钥,A
便用B
的公钥加密信息。 -
A
将这个消息发给B
(已经用B
的公钥加密消息)。 -
B
收到这个消息后,B
用自己的私钥解密A
的消息。其他所有收到这个报文的人都无法解密,因为只有B
才有B
的私钥。
c、特点
- 算法强度复杂
- 安全性较高
d、缺点
- 由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快
2、RSA
a、概念
RSA
算法基于一个十分简单的数论事实:将两个大质数(素数)相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
b、举例
取两个简单的质数:89、97,得到两者乘积很简单8633,但是要想对8633进行因式分解,其工作量成几何增加。
3、数字签名
a、场景
早上买杯10元的奶茶,得知使用店家的app
支付可以打5折,于是你用店家的app
支付了5元,马上收到消费5元的通知。看似一切正常,但是黑客拦截并获取客户端发送的请求支付的信息,将需要支付的5元改成50元并发送,终端收到支付50元的请求,扣款50元并将扣款信息返回给客户端,黑客再次拦截扣款信息,将已扣款50元改成已扣款5元后发送给客户端,哈哈哈一来一回你已经在不知不觉中损失了45元。
移动支付、金融流通越来越频繁,如何避免这样的问题,这就需要对敏感数据进行数字签名。
b、概念
数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。
将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH
函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。
c、流程
数字签名是个加密的过程,数字签名验证是个解密的过程。
流程图客户端
- 将敏感信息进行对称或非对称加密得到加密过得数据报文
- 将数据报文再进行一下
HASH
算法加密得到32位的HSAH
值 - 将32位
HASH
值进行RSA
算法加密得到签名 - 将签名和数据报文打包组合发送给服务端
服务端
- 将接收到的签名用私钥解密得到32位的
HASH
值 - 将接收到的数据报文按照客户端相同的操作进行
HASH
算法,得到32位HASH
值 - 判断两个
HASH
是否相同,如相同则表示数据报文是安全的 - 将数据报文解密得到需要的原始数据
五、使用RNCryptor框架
1、导入框架
❶ 将框架从RNCryptor-objc下载到本地后,将RNCryptor
和RNCryptor iOS
文件夹导入到工程中。
❷ 需要在Build Phases
的Link Binary With Libraries
中添加Security.framework
。
❸ RNCryptor iOS.h
文件中,需要将
#import <RNCryptor/RNCryptor.h">
#import <RNCryptor/RNDecryptor.h>
#import <RNCryptor/RNEncryptor.h>
#import <RNCryptor/RNCryptorEngine.h>
替换为:
#import "RNCryptor.h"
#import "RNDecryptor.h"
#import "RNEncryptor.h"
#import "RNCryptorEngine.h"
这样才可以正常编译。编译成功后,在需要使用的地方#import "RNCryptor iOS.h"
即可。
2、基本使用
#import "RootViewController.h"
#import "RNCryptor iOS.h"
@interface RootViewController ()
@end
@implementation RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 获得需要加密的数据
NSString *str = @"谢佳培";
NSLog(@"加密内容为:%@",str);
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
// 加密秘钥
NSString *privateKey = @"123456";
NSError *error;
// RNCryptor加密
NSData *encryptedData = [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password: privateKey error:&error];
// RNCryptor解密
NSData *decryptedData = [RNDecryptor decryptData:encryptedData withSettings:kRNCryptorAES256Settings password: privateKey error:&error];
// 转为字符串
NSString *resultStr = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
NSLog(@"解密内容为:%@",resultStr);
}
@end
输出结果为:
2020-09-09 10:38:53.132186+0800 RNCryptorDemo[56693:4712005] 加密内容为:谢佳培
2020-09-09 10:38:53.159468+0800 RNCryptorDemo[56693:4712005] 解密内容为:谢佳培
3、CXYRNCryptorTool mac端加密小工具
a、用途
CXYRNCryptorTool
是基于RNCryptor
的Mac
端加密小工具。开发中出于安全考虑需要对一些资源(png
、mp3
)加密,就可以使用RNCryptor
。CXYRNCryptorTool
能够帮你对资源文件先进行加密。
b、使用步骤
从CXYRNCryptorTool下载好工具,然后用Xcode运行,在弹出的窗口中放入图片,指定加密密码、格式以及存放地址。
CXYRNCryptorToolc、运行效果
运行效果d、使用RNCryptor进行解密
运行效果如下:
2020-09-09 14:00:57.643081+0800 RNCryptorDemo[59004:4830056] ======解开苹果!=====
2020-09-09 14:00:57.666878+0800 RNCryptorDemo[59004:4830056] ======解开瑞幸咖啡!=====
使用RNCryptor进行解密
❶ 将加密好的cxy文件导入项目中,注意需要Xcode无法识别这种格式的文件,所以无法自动导入资源到mainBundle
中,所以需要我们自己手动添加到Bundle Resource
,否则[[NSBundle mainBundle] pathForResource......
返回的结果为nil
。
❷ 使用之前约定的密码xiejiapei
对加密数据通过RNCryptor
工具进行解密。
// 针对 CXYRNCryptorTool mac端加密小工具 进行解密Demo
- (void)decryptCXYRNCryptorTool
{
NSData *encryptedAppleData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apple" ofType:@"cxy"]];
NSData *encryptedLuckcoffeeData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"luckcoffee" ofType:@"cxy"]];
NSError *appleError;
NSError *luckcoffeeError;
NSData *decryptedAppleData = [RNDecryptor decryptData:encryptedAppleData withPassword:@"xiejiapei" error:&appleError];
NSData *decryptedLuckcoffeeData = [RNDecryptor decryptData:encryptedLuckcoffeeData withPassword:@"xiejiapei" error:&luckcoffeeError];
if (!appleError)
{
NSLog(@"======解开苹果!=====");
UIImage *image = [UIImage imageWithData:decryptedAppleData];
self.appleImageView.image = image;
}
else
{
NSLog(@"======苹果坏了!=====");
}
if (!luckcoffeeError)
{
NSLog(@"======解开瑞幸咖啡!=====");
UIImage *image = [UIImage imageWithData:decryptedLuckcoffeeData];
self.luckcoffeeImageView.image = image;
}
else
{
NSLog(@"======瑞幸咖啡喝光了!=====");
}
}
4、PDF加解密
a、注意事项
- 考虑到PDF文件可能较大的原因,这里在加解密时使用了子线程,以避免加解密过程耗时。
- PDF查看需要提供路径。
b、准备工作
❶ 导入框架
#import "RootViewController.h"
#import "RNCryptor iOS.h"
#import "ReaderViewController.h"
#define Password @"xiejiapei"
@interface RootViewController ()<ReaderViewControllerDelegate>
@property(copy,nonatomic) NSString *filepath;// PDF文件存储路径
@end
❷ 导入PDFReaderSources文件夹和PDF文件
c、PDF加密
将网络请求下来的数据流(NSData
)直接进行加密,加密成功后存入沙盒目录中。
// PDF加密
- (void)PDFEncryption
{
__block NSData *encryptedData;// 用于加密的数据
__block NSError *error;// 出错
// 1.获取待加密PDF
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"天域苍穹小说.pdf" ofType:nil];
// 2.用于加密成功后存入的沙盒目录
NSString *fileEncryPath = [NSHomeDirectory()stringByAppendingPathComponent:@"/Documents/novel.xiejiapei"];
// 3.判断是否已存在加密文件,若存在直接执行解密过程
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:fileEncryPath])
{
// PDF解密
[self PDFDecryptedData:[NSData dataWithContentsOfFile:fileEncryPath]];
return;
}
// 4.异步去加密,防止占用太多内存
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 将PDF转化为数据
NSData *data = [NSData dataWithContentsOfFile:filePath];
// 对数据进行加密
encryptedData = [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:Password error:&error ];
// 加密成功
if (!error)
{
NSLog(@"^_^ PDF加密成功 ……——(^_^)\n");
NSLog(@"加密后数据为 == %@",encryptedData);
}
// 5.在主线程上写入加密后的文件到沙盒目录
dispatch_sync(dispatch_get_main_queue(), ^{
BOOL isSuccessfullyStored = [encryptedData writeToFile:fileEncryPath atomically:NO];
if (isSuccessfullyStored)
{
NSLog(@"加密文件写入到沙盒目录成功");
}
else
{
NSLog(@"加密文件写入到沙盒目录失败");
}
NSLog(@"写入PDF的路径:%@",fileEncryPath);
// 6.PDF解密
[self PDFDecryptedData:encryptedData];
});
});
}
d、PDF解密
在查看PDF时,先对加密的PDF进行解密,再将解密的PDF存入沙盒目录中(区分加解密PDF文件)。这里加解密时并没有具体实现网络请求模块,只是简单的对本地文件进行了实践,但大体实现过程已经实现。
// PDF解密
- (void)PDFDecryptedData:(NSData *)encryptedData
{
// 1.用于解密成功后存入的沙盒目录
NSString *fileDecryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"/Documents/novel"];
// 2.异步去解密,防止占用太多内存
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSError *error;// 出错
if (encryptedData != nil || Password != nil)
{
// 解密
NSData *decryptedData = [RNDecryptor decryptData:encryptedData withPassword:Password error:&error];
// 3.在主线程上写入解密后的文件到沙盒目录
dispatch_sync(dispatch_get_main_queue(), ^{
BOOL isSuccessfullyStored = [decryptedData writeToFile:fileDecryPath atomically:NO];
if (isSuccessfullyStored)
{
NSLog(@"解密文件写入成功");
NSLog(@"解密PDF写入的路径为:%@",fileDecryPath);
self.filepath = fileDecryPath;
[self pushReaderVC];
}
else
{
NSLog(@"解密文件写入失败");
}
});
}
else
{
NSLog(@"加密数据为空");
}
});
}
e、查看PDF
获取解密的PDF文件路径,查看PDF文件。
// 跳转到阅读器视图
-(void)pushReaderVC
{
// 1.实例化控制器
NSString *phrase = nil;
ReaderDocument *document = [ReaderDocument withDocumentFilePath:self.filepath password:phrase];
// 2.跳转到阅读器视图
if (document != nil)
{
ReaderViewController *readerViewController = [[ReaderViewController alloc] initWithReaderDocument:document];
readerViewController.delegate = self;
readerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
readerViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:readerViewController animated:YES completion:NULL];
}
else// 3.没有PDF文件
{
NSLog(@"%s [ReaderDocument withDocumentFilePath:'%@' password:'%@'] failed.", __FUNCTION__, self.filepath, phrase);
NSLog(@"没有PDF文件");
}
}
f、删除PDF
退出查看当前的PDF文件时,删除解密后的PDF文件缓存,保留加密的PDF缓存。
// ReaderViewControllerDelegate
- (void)dismissReaderViewController:(ReaderViewController *)viewController
{
// 退出查看PDF时删除解密存储文件
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:self.filepath error:nil];
[self dismissViewControllerAnimated:YES completion:nil];
}
g、运行效果
2020-09-10 11:08:20.578064+0800 RNCryptorDemo[72221:5403187] ^_^ PDF加密成功 ……——(^_^)
2020-09-10 11:08:20.578226+0800 RNCryptorDemo[72221:5403187] 加密后数据为 == {length = 21371410, bytes = 0x0301c6c9 244d913d 765b3348 42f22bc9 ... af608d91 e43bd8ad }
2020-09-10 11:08:20.590865+0800 RNCryptorDemo[72221:5402550] 加密文件写入到沙盒目录成功
2020-09-10 11:08:20.591008+0800 RNCryptorDemo[72221:5402550] 写入PDF的路径:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/82067702-CBE3-4F02-986F-160872522121/data/Containers/Data/Application/4BC50446-6428-4D5B-B106-A9CC9A453625/Documents/novel.xiejiapei
2020-09-10 11:08:20.724884+0800 RNCryptorDemo[72221:5402550] 解密文件写入成功
2020-09-10 11:08:20.725016+0800 RNCryptorDemo[72221:5402550] 解密PDF写入的路径为:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/82067702-CBE3-4F02-986F-160872522121/data/Containers/Data/Application/4BC50446-6428-4D5B-B106-A9CC9A453625/Documents/novel.pdf
PDF加解密
Demo
Demo在我的Github上,欢迎下载。
DataSecurityDemo