alvin hu

A blog of iOS developer.

通过SSL Pinning提高iOS应用的安全性

| Comments

前言

关于SSL Pinning的技术在国外有很多文章提到,但是还没有找到一篇相关的中文文档。因为在开发中碰到这个问题,所以查阅了很多相关文章,在此基于自己对SSL Pinning的理解,再融合一些其他资料,整理了一下写了这么一篇文章,既给自己做个备份,也给大家做个参考。写的好的给个掌声,写的不好请留言指正。

背景

在开发Client-Server架构的手机应用时,如果问到如何提高数据传输的安全性,肯定很多程序员第一时间会回答TLS/SSL。确实,对比普通的HTTP协议数据传输,SSL可以大大提高数据的安全性,因为所有传输过程中的数据都是经过加密的。如果用户在机场、咖啡厅等开放式的WIFI环境下使用应用,就算有攻击者通过技术手段截取了客户端与服务器之间传输的数据,对于他们来说也不过是一些加密过的垃圾。

表面上看这样已经足够安全了,但是危险就在身边。虽然SSL解决了内容加密的问题,但是在这个兵火战乱的年代,和你通信的是不是真正的服务器呢?就算对方能够提供证书,这年头证书也可以伪造,2011年就爆发了一次大规模的SSL证书安全攻击,受牵连的公司包括谷歌,还有大名鼎鼎的github.com(见下图)。

GitHub被攻击

下面我们介绍为什么有了证书用了加密还会被攻击吧!

中间人攻击

中间人攻击(Man-in-the-middle Attack,简称MITM、MitM、MIM、MiM、MITMA)是一种由来已久的网络入侵手段,并且在今天仍然有着广泛的发展空间,如SMB会话劫持、DNS欺骗等攻击都是典型的中间人攻击。简而言之,所谓的中间人攻击就是通过拦截正常的网络通信数据,并进行数据篡改和嗅探,而通信的双方却毫不知情。

具体中间人攻击的定义、方法和防御措施等可以参考维基百科或者百度百科。以下转载两个故事用来说明中间人攻击的大致流程。

西方言情简化版

从前有一枚高富帅叫杰克,女友是白富美女神爱丽丝,却苦于天各一方,只能求书信往来以解相思之苦。奈何时局动荡、人心不古,觊觎女神者比比皆是。书信若为明文,定要被那中转信件的屌丝皮特拆看,而后从中作祟,行不轨之事。

于是杰克与爱丽丝商定,从今而后,爱丽丝给杰克的书信均以密文誊写,再附以特制封签,杰克收到密信后可凭此封签在一公证可信之处验明此信是否确为爱丽丝所发。

商定完毕,二人书信往来再非明文。皮特拆看无果,甚是着急,情急之下心生一计:既然无法看到爱丽丝的密信内容,不如索性截下来信,自行伪造一封转交杰克,再设法从他那套取消息?

打定主意,皮特翻看了一下手中爱丽丝给杰克的一封密信:

封签:ecaa5d137be9468d98379ada45919d80
从哪来:爱丽丝
打哪去:杰克

[正文已加密]

皮特旋即扣下了这封信,伪造了另外一封交给杰克:

封签:718fe14088974488b821f8c9d8f14849
从哪来:爱丽丝
打哪去:杰克

亲爱的,上次你给我说你的银行卡帐号密码是多少来着?我一下忘记了。急用!

附:回信记得用密码「此地无银三百两」加密哟!

杰克收到篡改的信件后,看到爱丽丝急于盼复,未经细察,便回信告知了银行卡帐号密码。于是就悲剧了……

这就是最基础的中间人攻击。杰克如果谨慎一点,每次收到密信时都去那公证可信之处验证一下封签,确认信件是由爱丽丝所发,上面的悲剧就不会发生。

不过道高一尺、魔高一丈,皮特若能控制那公证可信之处,待杰克去验证时就可以做手脚,让杰克误以为伪造密信确为爱丽丝所发,于是继续悲剧……这种手法叫做伪造证书。

从这个故事可以看出中间人攻击就是躲在中间的小人,让通信的双方误以为跟自己通信的就是TA,而小人在中间获取双方的重要信息。

三国通信加强版

上面的故事虽然生动,但是与真正的SSL原理有一定出入,这是为了更容易理解。实际上通过SSL加密通信,一旦会话初始化完成,想要完成中间人攻击是非常困难的。TLS的中间人攻击是针对加密会话的初始化阶段进行的,而不是实际通信的阶段。加密通信的初始化阶段,需要通过非对称密码算法来协商密钥,然后用协商好的密钥,使用对称加密算法进行实际的通信。

不知道各位有没有见过一种锁,当用钥匙打开这种锁之后,钥匙就可以拔出来了,剩下一个开着的锁头。拿着这个开着的锁,没有钥匙,一旦锁上了就开不了。这是现实生活中的一种非对称加密的物件,能上锁,但是不能开锁。

现在故事开始,假设我们回到了三国时代,孙权需要和刘备通信,而且通信的内容必须要保密。于是负责替刘备接收消息的简雍想出来一个办法,找了一把上面说的那种锁(公钥),我们给它取名甲锁,先用钥匙打开,然后送给孙权。孙权在通信之前,需要找到另外一把有两个钥匙的锁(对称加密算法),我们给它取名乙锁,然后把这把乙锁和其中一把乙锁钥匙(对称密钥),放进一个无法被破拆的铁盒子里面,用简雍提供的那把开着的甲锁把铁盒子锁上。

这时候,这个铁盒子就无法打开了,除了拥有甲锁钥匙的简雍。这个装有乙锁和一把乙锁钥匙的铁盒子,可以放心地交到任何一个人手上,然后让他拿去给简雍。简雍拿到这个铁盒子之后,加密通信会话就建立了。他就会用甲锁钥匙打开铁盒子,取出乙锁和乙锁钥匙。刘备写下“亲,你好,我是刘备”的小纸条,放进铁盒子里,然后简雍用乙锁把铁盒子锁上,然后交回到孙权的手上。这个时候,铁盒子被孙权提供的乙锁锁上了,除了孙权和简雍,没有别人有钥匙能够打开这个铁盒子,铁盒子也就可以安全地经过邮差送到孙权的手上;孙权收到铁盒子之后,用自己的乙锁钥匙打开乙锁,读取铁盒子里面的消息,然后放进新的小纸条,再寄送回去。

以上是加密通信的过程。

接下来,别有用心的曹操出现了,他先冒充孙权的人向简雍要来了打开的甲锁。当孙权需要和刘备通信的时候,曹操再递给孙权一把他自己的丙锁,骗孙权说这把是简雍的甲锁。当孙权把自己的乙锁和乙锁钥匙放进去,交给曹操让他把这些送给简雍的时候,曹操就可以找到另外一个铁盒子,装上自己另外一把有两把钥匙的丁锁和其中一把丁锁钥匙,用真正的简雍提供的甲锁锁上,然后寄给简雍。简雍依然会正常的收到一个铁盒子,里面装着丁锁和一把丁锁钥匙,只不过,这个锁已经不是孙权的了,而是曹操的,孙权的那把乙锁实际上在曹操手上。简雍把“亲,你好,我是刘备”的小纸条放进铁盒,然后让曹操带回去给孙权,而这个时候,曹操就可以打开这个小铁盒,偷看他们之间的消息,然后自己编造一条消息,放进铁盒里面,然后传回去给孙权。

以上是中间人攻击。

为了避免中间人攻击,聪明的简雍,发明了一种神奇的、无法撕毁、涂改和变造的小纸条(数字签名),上面写着“这把锁经过简雍认证,是刘备加密通信专用锁”,然后贴在甲锁上,这样子曹操就不能伪造锁了,这时候这个锁叫作证书。

但是问题又来了,许多人不认识简雍,他们怎么知道简雍就是可信的,他认证的锁就是可用的?于是关羽在这个小纸条的下方又贴了一个小纸条,“简雍经过关羽的认证,可以对刘备加密通信专用锁进行认证”。关羽不仅可以认证锁,还可以认证简雍的权力,这时候关羽就是CA。可是问题还没有解决,还是有很多傻瓜不认识关羽,于是诸葛亮又在关羽的小纸条上又贴了一个小纸条,“关羽经过诸葛亮的认证,可以对刘备加密通信专用锁进行认证”。问题依然没有解决,还是有白痴不认识诸葛亮,于是这时候需要一个权威的、人们无条件相信的人——汉献帝来对诸葛亮进行认证,这就是根CA,他贴上去的小纸条就叫作根证书。

以上是信任体系。

最后一个问题,SSL的中间人攻击怎么实施。这时候糜芳出场了,因为是刘备的手下,具有一个可信的证书,类似于“糜芳经过关羽的认证,可以对刘备加密通信专用锁进行认证”。于是他自己伪造了一个锁,然后利用上一级CA对他的信任,去骗取孙权使用他提供的锁初始化加密会话。因为他的锁上面有上一级CA的认证,所以孙权会认为这个锁是可信的,而实际上糜芳通过自己拥有的证书,可以实施中间人攻击,窃取孙权和刘备之间通信的内容。

以上便是SSL的中间人攻击。可以看出,SSL的中间人攻击是比较复杂的,一般利用的是上一级可信CA颁发一个伪造证书来进行攻击。

解决方案

SSL Pinning(又叫Certificate Pinning)可以理解为证书绑定。在一些应用场景中,客户端和服务器之间的通信是事先约定好的,既服务器地址和证书是预先知道的,这种情况常见于CS架构的应用中。这样的话在客户端事先保存好一份服务器的证书(含公钥),每次请求服务器的时候,将服务器返回的证书与客户端保存的证书进行对比,如果证书不符,说明受到中间人攻击,马上可以中断请求。这样的话中间人就无法伪造证书进行攻击了。

接着前面的故事,为了防范曹操各种手段的攻击,简雍派亲信送给孙权一把贴有简雍认证的小纸条的甲锁,然后告诉孙权,这才是真正的小纸条和甲锁,凡是跟这不一样的锁都是假的。过不了几天,孙权想给刘备写信了,糜芳拿着一个贴着“糜芳经过关羽的认证,可以对刘备加密通信专用锁进行认证”小纸条的戊锁来了,孙权一比对发现跟简雍给自己的甲锁不一样,勃然大怒,又是一个奸细,给我推出去斩了。从此,孙权和刘备可以安全快乐的进行通信了。

SSL Pinning可以很好的解决中间人攻击的问题,但是仅限于在客户端预先知道服务器地址和证书的场景下。如果是在服务器地址未知如需要用户手动输入服务器url的场景中,SSL Pinning就无法发挥作用了。

在我开发的一个iOS项目中使用了AFNetworking做为网络应用层,AFNetworking已经很好的支持了SSL Pinning。使用起来非常简单!

  • 首先在Prefix.pch文件最后加上:
1
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_

如果是自签名的证书还需要加上:

1
#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_

否则在请求服务器的时候会弹出提示框说该服务器证书为不可信任的机构颁发

  • 然后把服务器证书添加到应用中,证书文件取什么名字无所谓,后缀必须是cer,并且是DER编码的证书

如何制作自签名证书和转换DER编码证书文件(补充章节)可以查看我之前的一篇文章:IIS8中使用OpenSSL来创建CA并且签发SSL证书

  • 最后在AFHTTPClient的继承类中加上一行代码:
1
[self setDefaultSSLPinningMode:AFSSLPinningModePublicKey];

具体位置参考如下:

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
#import "HPAPIClient.h"
#import "AFHTTPRequestOperationLogger.h"

@implementation HPAPIClient

+ (HPAPIClient *)sharedClient
{
    static HPAPIClient *sharedClient = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        sharedClient = [[HPAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kAPIBaseURL]];
    });
    return sharedClient;
}

- (id)initWithBaseURL:(NSURL *)URL
{
    self = [super initWithBaseURL:URL];
    if (self) {
        [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
        [self setDefaultHeader:@"Accept" value:@"application/json"];
        [self setDefaultSSLPinningMode:AFSSLPinningModePublicKey];
    }
    return self;
}
...

总结

有了SSL+SSL Pinning,在开发CS架构的应用中就可以大大提高网络数据传输的安全性,简化程序开发中其他的一些安全措施。我自己是感觉方便安全多了!

References

Comments