iOS网络高级编程—高级网络技术(保护数据传输)

Apple封装了两个相关的API集合,分别是Security框架和CommonCrypto接口集合。

  • Security框架。是一套C API的集合,用于管理证书、信任策略以及对设备安全数据存储的访问。官方文档:Security Framework Reference
  • CommonCrypto是一套接口集合,用于数据的加密解密、生产常见的密码散列,计算消息认证码等。官方文档:iOS Manual Pages

1、验证服务器通信

为保证客户端与目标服务器的正常通讯,客户端一般都要与服务器进行验证。这个时候一般采用HTTPS协议,并使用443接口。而iOS已经封装了一个认证空间的类NSURLProtectionSpace。其初始化方法如下:

1
NSURLProtectionSpace *defaultSpace = [[NSURLProtectionSpace alloc]initWithHost:@"targetservice.com" port:443 protocol:NSURLProtectionSpaceHTTPS realm:@"mobile" authenticationMethod:NSURLAuthenticationMethodDefault];

由初始化方法可以看出, NSURLProtectionSpace主要有Host、port、protocol、realm、authenticationMethod等属性。

当客户端向目标服务器发送请求时。服务器会使用401进行响应。客户端收到响应后便开始认证挑战,而且是通过willSendRequestForAuthenticationChallenge:函数进行的。

过程示意图如下:

认证challenge(图片来自csdn博客)

willSendRequestForAuthenticationChallenge:函数中的challenge对象包含了protectionSpace实例属性,在此进行保护空间的检查。当检查不通过时既取消认证,这里需要注意下的是取消是必要的,因为willSendRequestForAuthenticationChallenge:可能会被调用多次。

2、HTTP认证

在完成服务器的认证后,接下来就是对客户端的认证了。

2.1、标准认证

HTTP标准认证一般有以下三种:HTTP Basic、HTTP Digest、NTLM。

  • HTTP Basic。顾名思义,就是很基本的认证。由RFC1945定义。明文传输(不知道谁想的,这也叫认证)。但是如果搭配SSL的话,可以弥补一定的缺点。那不成了HTTPS。。。?
  • HTTP Digest。更安全的认证。由RFC2069定义。会在传输前对密码尽心MD5哈希处理。并会有一个密码随机数配合(IV?),这个随机数会对消息进行签名,使用一次之后就会标记为过期不能使用。可以防止重放攻击(重新发送之前的密文)。有趣的是HTTP Basic与HTTP Digest的定义已经合并成立一个新的标准RFC2617。
  • NTLM。微软提出的安全协议。类似于前两者的响应协议。据说很大程度上被Kerberos系统取代了。不过还是有备继续用于认证web上的远程用户。
    前面介绍了挑战的检查,在完成检查之后就需要进行客户端的认证了。这个时候就要用到NSURLCredential(Credential,凭据)。作用有:可以表示由用户名/密码组合、客户端证书及服务器新人创建的认证信息。

基本的创建及使用代码:

1
2
NSURLCredential *creds = [[NSURLCredential alloc]initWithUser:@"user" password:@"password" persistence:NSURLCredentialPersistenceForSession];
[challenge.sender useCredential:creds forAuthenticationChallenge:challenge];

challenge来自willSendRequestForAuthenticationChallenge:。该方法也应用在willSendRequestForAuthenticationChallenge:函数内。

NSURLCredential有个NSURLCredentialPersistence参数需要注意下。这是个枚举类型。包括了以下枚举常量:

  • NSURLCredentialPersistenceNone。不存储
  • NSURLCredentialPersistenceForSession。按照Session生命周期存储
  • NSURLCredentialPersistencePermanent。存储到钥匙串
  • NSURLCredentialPersistenceSynchronizable。存储到钥匙串,根据相同的AppleID分配到其他设备
    在发送认证,服务器认证成功后,会返回一个证书,客户端需要保存以便后续使用。这个证书可能被编码成PKCS格式。这时候要用到SF框架的SecPKCS12Import函数。可参考这篇blog:iOS **证书、密钥及信任服务**

3、使用哈希与加密确保消息完整性

在顺利完成认证过程后,客户端与服务器便可开始数据通讯了。但为了安全这个时候发送的消息是要进行加密的。

这里采用的客户端安全通讯过程是:负载哈希(散列)、生成MAC及IV、加密负载并传输。

服务器:解密负载、生成MAC并对比。

3.1、哈希

哈希,即散列,Hashing。对数据进行哈希可以简化数据块的比较与排序。常见的使用场景包括追踪文件变更、下载校验、数据混淆以进行数据库存储、验证请求数据的完整性等。

iOS的CommonCrypto库提供了预定义的哈希枚举,我们可以利用这个来进行简单的哈希操作,比如数据校验。

如下为一个封装函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedef NS_ENUM(NSInteger,TUHashType){
TUHashTypeMD5 = 0,
TUHashTypeSHA1 = 1,
TUHashTypeSHA256 = 2
};
-(NSString *)hashWithType:(TUHashType)type withData:(NSString *)data{
const char * ptr = [data UTF8String];

NSInteger bufferSize;
switch (type) {
case TUHashTypeMD5:
bufferSize = CC_MD5_DIGEST_LENGTH;
break;
case TUHashTypeSHA1:
bufferSize = CC_SHA1_DIGEST_LENGTH;
break;
case TUHashTypeSHA256:
bufferSize = CC_SHA256_DIGEST_LENGTH;
break;
}
unsigned char buffer[bufferSize];
switch (type) {
case TUHashTypeMD5:
CC_MD5(ptr, (int)strlen(ptr), buffer);
break;
case TUHashTypeSHA1:
CC_SHA1(ptr, (int)strlen(ptr), buffer);
break;
case TUHashTypeSHA256:
CC_SHA256(ptr, (int)strlen(ptr), buffer);
break;
}
NSMutableString *hashString = [NSMutableString stringWithCapacity:bufferSize *2];
for(int i=0;i<bufferSize;i++){
//%02x,格式控制:以十六进制输出,2为指定的输出字段的宽度.如果位数小于2,则左端补0
[hashString appendFormat:@"%02x",buffer[i]];
}
return hashString;
}

3.2、消息认证码MAC

顾名思义就是对消息进行认证(认证简直没完没了)。为什么要有这个认证码呢,是因为传输给服务器的负载可能在中途被窃取替换了,我们可以通过这个消息认证码来对比下是否原数据。客户端会随负载传生成好的MAC过来。服务端也会根据相同的算法、相同的密钥生成一个MAC,跟客户端的对比,就能判断负载是否被替换了。

MAC可以用很多种算法,这里采用的是前面介绍的Hash算法,基于Hash的MAC也叫HMAC。同时Hash算法也有很多种,常用的又MD5、SHA-1、SHA-256等,这里采用SHA-256。

一个简单的封装函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(NSString *)hmacWithKey:(NSString *)key withData:(NSString *)data{
const char *ptr = [data UTF8String];
const char *keyPtr = [key UTF8String];
unsigned char buffer[CC_SHA256_DIGEST_LENGTH];

CCHmac(kCCHmacAlgSHA256, keyPtr, kCCKeySizeAES256, ptr, strlen(ptr), buffer);

NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for(int i=0;i<CC_SHA256_DIGEST_LENGTH;i++){
[output appendFormat:@"%02x",buffer[i]];
}

return output;
}

这个函数与前面的Hash通用函数有些类似。但认真看可以发现不同点,MAC函数有密钥key参数,限制为了SHA-256算法,而且底层加密算法是CCHmac()。

3.3、加密

在完成MAC的生成之后,便可以开始最后的负载加密了。之前有篇文章也介绍过加密,传送门:Android与iOS平台实现SHA-1、3DES的后台对接

iOS原生的CommonCryptor库支持了大部分的加密算法。用到的函数是CommonCryptor中的CCCrypt()。

这里介绍一个优秀的开源加密库:CocoaSecurity(from GitHub)

4、在设备上安全存储认证信息

2、HTTP认证中说有在完成认证后要将证书存储起来,但是储存在哪里呢?iOS储存机制中有很多方法。iOS的Security Framework提供了更加安全的Keychain Services API来处理这种情况。

Keychain有一个特别的优点是程序删除或替换后也还在。这个特点衍生了另一个有用的技术:iOS设备的UUID识别,参考文章:iOS的UDID废用以及UUID配合keychain的替换方案实现

关于keychain这里不做更多介绍,可参考这篇文章:keychain的使用

此外GitHub上还有一个方便的开源工具库:UICKeyChainStore

至此,本文到此就结束了。