前言
如今,信息安全越来越成为互联网的一个热议的话题,互联网用户也越来越重视保护网络中的个人隐私和个人信息。软件的良好的安全机制让运营者和使用者都受益。因此,信息安全成为了开发者与网络中不法分子的一场博弈。
1.http与https
http与https 都是Web浏览器与服务器交互的数据传输协议。通过http进行传输的数据都是明文,没有进行任何的加密,如果传输的数据中包含银行卡号、密码等敏感信息,而且都没有进行加密,那么极易被不法份子或者黑客通过数据拦截而获取,这样就存在很大的安全隐患。
为了解决http的这一缺陷,推出了https(安全套接字层超文本传输协议)。https是在http的基础上增加了SSL协议,客户端Web浏览器与服务器之间传递的所有数据都会被加密的。
2.https工作流程
客户端在使用https方式与Web服务器通信时有以下几个步骤,如图所示。
(1)客户使用https协议的URL发起Web服务器请求,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。
3.项目适配
3.1.Web请求调整(NSURLRequest、NSMutableURLRequest)
https请求有两种类型:域名和ip。
举例如下:
https://zone.qq.com/login.aspx?user=xx&password=xx
https://192.168.67.35/login.aspx?user=xx&password=xx
我们知道互联网中通过http协议进行数据传输都是根据双发的ip进行标记唯一对象的。而使用域名只是为了用户方便记忆网络地址,通信双方在进行数据交换之前都会将域名地址通过DNS(Domain Name System,域名系统)中的域名解析协议进行ip映射。因此,同一网站,不管你输入域名地址和ip地址,正常情况下都是可以访问的。
iOS开发中,进行http和https数据请求的对象是NSURLRequest和NSMutableURLRequest,在发起数据请求之前,需要配置其host
值域,该值为地址访问对应的证书中的域名。
如上例中所示,host = zone.qq.com。
3.2.SSL验证逻辑调整
在iOS开发中,可发起https对象请求的有以下对象:
NSURLConnection
NSURLSession
NSWebView
3.2.1.NSURLConnection
使用NSURLConnection进行https站点连接时,需要处理SSL验证,NSURLConnectionDelegate中提供了如下方法来处理这些验证逻辑。
- connection: canAuthenticateAgainstProtectionSpace:
- connection: didReceiveAuthenticationChallenge:
具体示例如下:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
3.2.1.1.接受任何证书
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
3.2.1.2.接受私有证书
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
static CFArrayRef certs;
if (!certs) {
NSData *certData =[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"证书名称" ofType:@"cer"]];
SecCertificateRef rootcert =SecCertificateCreateWithData(kCFAllocatorDefault,CFBridgingRetain(certData));
const void *array[1] = { rootcert };
certs = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
CFRelease(rootcert);
}
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
int err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, certs);
if (err == noErr) {
err = SecTrustEvaluate(trust,&trustResult);
}
CFRelease(trust);
BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed)||(trustResult == kSecTrustResultConfirm) || (trustResult == kSecTrustResultUnspecified));
if (trusted) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}else{
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
3.2.2.NSURLSession
类似于NSURLConnection, NSURLSessionDelegate中也提供了代理方法来处理验证逻辑。
- NSURLSession: didReceiveChallenge:CompletionHandler:
具体示例代码如下:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
NSString *trustDomain = @"request配置的host";
if (trustDomain) {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:trustDomain]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
NSMutableArray *polocies = [NSMutableArray array];
if (domain) {
[polocies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[polocies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)polocies);
SecTrustResultType bindResult;
SecTrustEvaluate(serverTrust, &bindResult);
return (bindResult == kSecTrustResultUnspecified || bindResult == kSecTrustResultProceed);
}
3.2.3.UIWebView
UIWebView不同于以上两个对象,没有预留相关的代理方法来实现https证书验证。
但是,我们可以借助NSURLConnection对象发起请求以验证证书。验证逻辑示意图如下:
通常,我们使用UIWebView页面加载时使用的是http请求,直接使用loadRequest:进行请求操作,流程如图中虚线所示。
现调整为https协议的请求,具体的页面加载和证书验证流程如下步骤:
(1)在UIWebViewDelegate的代理方法webView: shouldStartLoadWithRequest: navigationType:中取消Web页面加载请求,创建NSURLConnection对象并发起https请求。
(2)在NSURLConnection中进行证书验证,具体如3.2.1。
(3)在NSURLConnection的代理方法connection: didReceiveResponse:中恢复Web请求。
具体实现代码如下:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSLog(@"开始加载: %@ 授权:%d", [[request URL] absoluteString], _authenticated);
if (!_authenticated) {
_urlConnection = [[NSURLConnection alloc] initWithRequest:_requestW delegate:self];
[_urlConnection start];
return NO;
}
return YES;
}
NSURLConnection 中处理SSL验证
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
if ([challenge previousFailureCount] == 0){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
恢复UIWebView请求
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
_authenticated = YES;
[self loadRequest:_requestW];
[_urlConnection cancel];
}
4.讨论
4.1.配置本机host
在XCode模拟器上使用ip发起https请求时,需要配置本机的host,使用命令vim /etc/hosts
编辑并保存host配置文件。
4.2.证书验证次数
在项目https适配的过程中发现,当在局域网内使用https请求是,同一ip,只需要验证一次证书,而在公网使用https请求时,每次请求都需进行这书验证。
因此建议,不管在局域网和公网,都按照每次都需验证证书的逻辑来处理。
4.3.https请求耗时
从https工作原理可以看出,https的流程要比http多了几个步骤,因此,总体来说,每次的https请求延迟要比http延迟时间要长,因为涉及到证书验证和数据的加解密过程。
查阅相关参考资料,https的SSL握手(64ms)大概是TCP握手(22ms)耗时的3倍左右。
毫秒级的延迟,对用户来说是基本感觉不到的,因此可以忽略不计。
5.参考资料
http://www.cocoachina.com/ios/20150810/12947.html
http://www.ttlsa.com/web/ssl-delay-how-long/
在阅读的过程中,如若对该文章有技术错误或值得优化的建议,欢迎大家加QQ:690091622 进行技术交流和探讨。