iOS

IOS基础:数据安全

2020-10-22  本文已影响0人  时光啊混蛋_97boy

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

一、简介

1、需求

在开发应用的时候,数据的安全性至关重要。我们会要求开发者不允许明文传输用户的隐私数据,同时也不允许明文保存用户的隐私数据;这就需要我们去对敏感的数据进行加密处理。

对于iOS持久化方面,无论使用userDefaultplist文件、归档、数据库等存储方式,都需要考虑一个问题,如何安全的存储数据?对于这个问题,可以对数据加密保存。

2、加密方案选择

a、常见的加密算法
b、选择加密算法

面临了一个秘钥安全的问题,一般来说,我们可能会把秘钥硬编码到代码中,但我们的二进制文件存在一个字符串常量区,只要这个字符串在代码中被引用了,那么编译器就会把其写进字符串常量区。如果这样,稍微的分析下二进制文件,就可能把秘钥获取。所以秘钥硬编码是比较危险的。面对这个问题,可以通过把秘钥分拆成多个字符串,再通过某些处理生成真正的秘钥,来提高安全性。

从安全范围的角度去考虑,如果在每一个客户端都是同样的加密算法,同样的秘钥,那么只要其中一个被破解,整个大厦立马倒塌。为此,可以给安卓和苹果分别分配不同的秘钥,这样就能把两个平台,这块的安全性隔离。既然有了这样的想法,那么前进一步,如果每个客户端都拥有不同的秘钥,并且是一种随机秘钥(随机安全性更高,当然可能伪随机,虽然伪随机实际上是通过算法来生成的,有规律,但其破解难度很大),那么就能把这块的安全性做到客户端隔离。

3、建议的加密方案

方案
优点

缺点:由于客户端隔离,秘钥都不一样,数据无法统一分析


二、哈希(散列)函数

1、简介

a、哈希的用途

哈希主要用于验证,就像人的指纹一样。涉及到验证的部分一般都可以使用Md5。如:向百度云上传文件,百度云对每个文件都进行MD5加密,将你上传文件的MD5与已有的进行比对,假如已经存在则说明你上传的文件在库里已经有了,直接秒传。一般来说相同的文件库里面只会保存一份。

b、哈希破解(散列碰撞)

不同的数据MD5加密后得到的应该是不同的32位字符,但是32位字符排列组合的个数是有限的,但是数据是无限的,比如自然数字。对无限的数据MD5加密得到有限个32位字符,其中肯定是有重复的,即不同的数据哈希之后可能得到相同的结果。

2、MD5 散列函数

a、特点
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解密网站 针对md5sha1等全球通用公开的加密算法进行反向查询,通过穷举字符组合的方式,创建了明文密文对应查询数据库,创建的记录约90万亿条,占用硬盘超过500TB,查询成功率95%以上,所以这种方式是不安全的,相对来说比较容易破解。

MD5解密网站

随着 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解密之后,很容易发现规律从而破解。

Salt
f、HMAC 散列

HMAC是个使用一个密钥加密并且两次的散列!

❶ 以用户账号登录注册为例

注册:

  1. 客户端向服务器发起注册请求,这时服务器生成一个密钥key(并保存),然后将密钥返回给客户端。
  2. 客户端根据密钥对密码进行HMAC加密生成密文,传给服务器,服务器保存(不允许明文保存用户的隐私信息)。
  3. 服务器返回注册成功后,客户端将密钥保存到手机钥匙串(减少密钥传递次数,提高安全性)。

登录:

  1. 客户端读取手机钥匙串中的密钥,对密码进行HMAC加密后,向服务器发出登录请求。
  2. 服务器根据账号读取数据库中的密文与客户端提交的进行比较。
❷ 算法
/** 计算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进制, 比如aascll码为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演示

准备工作
❶ 导入辅助文件
❷ 封装成工具类

考虑到加密对象和使用场景,理所当然的将加密过程放到了NSString的类别中,即下面说到的NSString+AES

❸ 与后端接口保持一致
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、流程
  1. A要向B发送信息,AB都要产生一对用于加密和解密的公钥和私钥
  2. A的私钥保密,A的公钥告诉BB的私钥保密,B的公钥告诉A
  3. A要给B发送信息时,因为A知道B的公钥,A便用B的公钥加密信息。
  4. A将这个消息发给B(已经用B的公钥加密消息)。
  5. 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、流程

数字签名是个加密的过程,数字签名验证是个解密的过程。

流程图
客户端
服务端

五、使用RNCryptor框架

1、导入框架

❶ 将框架从RNCryptor-objc下载到本地后,将RNCryptorRNCryptor iOS文件夹导入到工程中。

导入文件夹

❷ 需要在Build PhasesLink Binary With Libraries中添加Security.framework

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是基于RNCryptorMac端加密小工具。开发中出于安全考虑需要对一些资源(pngmp3)加密,就可以使用RNCryptorCXYRNCryptorTool能够帮你对资源文件先进行加密。

b、使用步骤

CXYRNCryptorTool下载好工具,然后用Xcode运行,在弹出的窗口中放入图片,指定加密密码、格式以及存放地址。

CXYRNCryptorTool
c、运行效果
运行效果
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

image.png

❷ 使用之前约定的密码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、注意事项
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

参考文献

iOS 本地数据安全存储方案探索
Base64原理解析
iOS加密详解
iOS中关于图片、PDF等文件加密

上一篇下一篇

猜你喜欢

热点阅读