本文前半部分介绍HTTPS的概念以及工作原理,后半部分着重介绍IOS网络请求怎么使用HTTPS
HTTPS概念
- HTTP: Hyper Text Transfer Protocol;
- HTTPS: Hyper Text Transfer Protocol over Secure Socket Layer;
我们看到HTTPS与HTTP相比,简称上多了一个S,名称上面多了一个Secure Socket Layer,实际上HTTPS=HTTP+SSL/TLS
HTTP协议的概念以及工作原理我们前面的文章已经介绍的很详细了,我们知道HTTP是应用层协议,建立在传输层协议(TCP/IP)上,那么HTTPS就是在应用层协议(HTTP)与传输层协议(TCP/IP)中间增加一个安全加密层(SSL/TLS)
在没有SSL/TLS层的时候,传输层(TCP/IP)发送的报文的数据域是明文的HTTP报文,当有SSL/TLS层的时候,传输层(TCP/IP)发送的报文的数据域是被SSL/TLS层加密的HTTP报文
在没有SSL/TLS层的时候 应用层获取到的数据是明文HTTP报文,当有SSL/TLS层的时候,应用层,收到的数据需要经过SSL/TLS层的处理,还原HTTP报文
上面只是用通俗的语言简单的介绍了一下HTTPS的概念,当然其工作原理要复杂很多,主要的工作都集中在了SSL/TLS层,通过这里我们也能看出,计算机网络这种分层架构设计的巧妙,增加中间一层,其他层几乎不需要改动就能增加新的特性
SSL/TLS
概念
SSL (Secure Socket Layer,安全套接字层),由网景公司发明,用来解决HTTP协议的安全问题(明文传输,容易被窥探,篡改等),SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层,SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
TLS (Transport Layer Security,传输层安全协议),它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1。所以TLS实际上是对SSL的补充以及增强,具体差异这里不表。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
所以我们经常看到一些文章会并列的提到两个概念,实际上,我们可以把这两个概念视为同一种协议的不同阶段的名称,下文我们对SSL/TLS的讨论不会深入到协议细节,只讨论大概的工作原理,方便大家理解,同时,将SSL/TLS作为一种协议看待
SSL/TLS协议的主要功能
- 确认客户端和服务器身份,并且协商加密秘钥(握手协议)
- 加密数据,防止数据被窃取,篡改(记录协议)
握手协议
握手协议主要用来在通讯开始前,确认双方身份,协商秘钥(用于后期加密通讯数据)
由于非对称加密的速度比较慢,这里的秘钥传输使用非对称加密,后期通讯数据,使用协商的秘钥对称加密。
所以在握手协议的四次握手过程中,我们最重要的就是确认了双方身份(验证证书),合成了会话秘钥,合成会话秘钥需要三个随机数,并且服务器公钥加密传输第三个随机数,用三个随机数合成会话秘钥
握手流程图如下
第一次握手(客户端发起)
客户端发送请求,客户端向服务端发送如下信息
- 客户端支持的TLS协议版本号
- 客户端生成的随机数,用于生成最终的会话秘钥
- 支持的加密方式,如RSA
- 支持的压缩方式
如果服务端不支持TLS版本,或者不支持我们发送的加密方式,那么直接返回失败,通讯结束
会话秘钥由三个随机数组成,在后面的会话中由各方提供
第二次握手 (服务端发起)
服务端收到客户端的请求后,向客户端回应如下信息
- 确认TLS版本
- 服务端生成的随机数,用于生成最终的会话秘钥
- 确认加密方式
- 服务器证书
- 需要客户端证书(可选)
服务端确认TLS版本与加密方式,如果不支持的话,则通信结束关闭链接,另外,如果服务端需要确认客户端身份,需要增加携带客户端证书的信息
第三次握手 (客户端发起)
客户端收到服务端回应后,必须要验证服务器的数字证书,一般来说,服务器的数字证书都是从CA机构申请得到的(收费),也有的是使用自建证书(免费),所以我们验证证书也要区分这两种情况,如果是自建证书的话,调用系统api验证证书肯定是失败的,需要我们自己验证。有关数字证书的内容会在下面讨论。
证书验证失败,如果是浏览器,会弹出警告框,选择信任,会继续通讯,如果是app请求接口,需要我们自己处理是否信任。
证书验证成功,说明服务器是可以信任的,从证书中取出服务端公钥,客户端回应服务端如下内容
- 客户端随机数,并用服务器证书里面的公钥加密
- 客户端证书 (如果服务端需要)
- 握手结束通知
上面的随机数是整个握手阶段的第三个随机数,并用服务器证书中的公钥加密。如果服务端需要验证客户端身份,客户端需要讲证书附带发送
第四次握手 (服务端发起)
服务器收到客户端回应,用私钥解开第三个随机数,然后用三个随机数”合成”最终会话秘钥,如果需要验证客户端证书,则验证客户端证书。然后回应客户端
- 握手结束
接下来进入正式的通讯阶段,通讯仍然使用HTTP协议,但是报文内容用上面生成的“会话秘钥”对称
加密
数字证书
上面握手过程中最重要的一步就是如何确认服务端身份,也就是如何验证服务器的数字证书,只有确认证书,我们才能保证我们通信的对方是我们信任的。
并且从证书中,取出公钥用于对称加密客户端传递的第三个随机数,保证最终三个随机数合成的会话秘钥不被破解。
要想理解数字证书的概念必须同时理解,数字签名,非对称加密,摘要算法这几个个概念,一般非对称加密我们使用RSA加密算法,摘要算法使用MD5,这两种算法都已经很普及了,不做过多介绍。
数字签名
一般来说,签名一定是签到某种内容载体上面,用来确认这个内容的真实,有效性,比如我们刷信用卡的时候需要签名,要把名字签到票据上面,来确认确实是本人消费。比如合同上面的签名,用来确认双方都承认合同的法律效应。
上面举的例子中,签名的载体是票据,合同书,签名的内容是我们的名字。
那么数字签名也是同样的逻辑,只不过,签名的内容不再是我们的名字,而是通过算法得到的一个字符串,这里的算法就是首先对载体的内容进行md5的到摘要,然后用签名人的私钥对摘要加密的到的就是数字签名,为什么要先得到摘要再用私钥签名呢,因为非对称加密是比较耗时的,由于内容长度不固定,可能很长,如果直接对内容进行非对称加密,效率会非常低,由于摘要算法得到的字符串长度是固定的,所以,能极大的减轻非对称加密的负担
比如我们想对一篇word文档进行数字签名,那么首先对整篇文章的内容进行md5的到摘要,然后用我的私钥对摘要进行加密的到的字符串就是数字签名,我们把数字签名附在文章最后。这样就完成了对这篇文章的数字签名
数字签名有什么用
我们可以看到即使已经有数字签名的文章,仍然是明文,仍然能被别人看到,所以数字签名的作用不是用来保证数据不被窥探,而是用来保证数据的完整性,保证数据不被篡改。
想象我已经将我的公钥发给了小王,然后我对文章进行数字签名,并将签名之后的文章发给小王,小王拿到文章之后,首先对文章内容进行md5的到摘要1,然后用我的公钥对数字签名解密的到摘要2,如果摘要1与摘要2相等,那么证明我发给小王的文章是没有经过篡改的。
其实这里面还有一个另外的问题,小王怎么确定手里的公钥就是我的,假如小王手里的公钥被小张偷换成自己的,那么小张截获我发送的文章后,进行篡改,用自己的私钥进行数字签名,小王拿到文章后,以为用的是我的公钥,其实用的是小张的公钥,验证数据是没问题的。这样就导致了数据被非法篡改。要想避免这种情况就需要引入数字证书。
数字证书
在日常生活中,我们也会接触到各种各样的证书,毕业证书,英语4,6级证书,等等,所有证书都是用来证明某些内容的真实性,注意,这里必须有一个公开的,具有公信力的第三方来做担保,比如英语四六级证书是教育部盖章做公证,如果我自己做一个四六级证书,并盖我自己的章,那恐怕没有人相信。数字证书也是同样的道理,数字证书保证证书申请人的信息,公钥是真实,可靠的,并且需要有公信力的第三方机构来做担保(数字签名)。
数字证书的内部格式是由CCITT X.509国际标准所规定的,它主要包含了以下信息
- 证书申请人(拥有人)的信息
- 证书申请人(拥有人)的公钥
- 证书有效期
- 颁发证书的单位
- 颁发证书的单位对证书的数字签名
- 证书的扩展信息
理论上,任何人都能颁发证书,我们可以自己为自己颁发一个证书(上面说的自建证书),但是这样的证书是不具备公信力的。
我们平常所说的数字证书都是一些权威的机构颁发,这些证书颁发机构叫CA,如国际知名的VeriSign,这些机构都是世界上指定的公认的,可信的具有证书颁发权限的机构。
我们拿到一个数字证书如何验证其是有效,可信的呢
- 首先查看有效期,如果在有效期内,继续验证
- 接着查看证书颁发机构是否是CA机构,一般来说,浏览器会内置绝大部分CA的根证书,如果是app的话,手机系统会内置绝大部分CA的根证书,当我们知道证书颁发机构的名称后,就会去我们内置的信任证书列表寻找是否有匹配的机构,如果有取出该机构的公钥。对数字证书的内容进行md5的到摘要1,用公钥对证书的数字签名进行解密的到摘要2,如果摘要1等于摘要2那么这个证书确实是合法的。所以证书里面申请人的公钥是可以信任的。
- 如果证书颁发机构不是CA,那么这个时候的处理方式完全取决于客户端,如果是浏览器,会弹出警告框,选择信任,继续通信,否则断开通信,如果是app那么我们要自己决定是否信任。
- 有得app服务器没有从CA申请证书,而是使用自建证书,这就需要把自建证书打包到app内部, 在调用系统验证证书的时候会返回失败,这时用打包到app内的证书与服务端返回的证书比较,如果一致的话,说明身份正确
另外数字证书的验证可能会是链式验证,因为证书的颁发机构会分为一级证书颁发机构,二级证书颁发机构,三级证书颁发机构,所以我们可能会一层一层往上验证到最顶层的证书颁发机构,最顶层的证书颁发机构是自验证的,即自己给自己颁发证书
现在来看上面数字签名最后抛出的问题,我们在文章的最后除了添加数字签名外,还要附上我从CA申请的数字证书,这样,小王在拿到文章后,先验证我的数字证书,首先要去CA机构的官网获取CA公钥,然后参照上面的方式验证我的数字证书,证书合法后,证明证书里面的公钥确实是我的,从证书中取出我的公钥,然后进一步验证数字签名。最后判断文章是否被篡改。
增加了数字证书后,我的公钥的真实性就能得到保证
IOS 适配HTTPS
理解了HTTPS的工作原理,再来看一下IOS 网络请求中是如何实现HTTPS的,就会变的很简答吗,IOS中的网络框架,为我们做了很多,我们需要做的只是对第三次握手的控制,当第二次握手服务器回应后,会携带服务器证书,这时候我们会收到回调,我们可以自定义判断证书的逻辑,如果我们压根不想判断证书,那么我们什么都不需要做就能支持HTTPS
下面分别介绍 NSURLSession 以及AFNetworking 两种方式下如何实现HTTPS
NSURLSession
实现如下代理,当第二次握手服务端回应后会调用,下面是用系统的信任证书列表来验证服务器数字证书
1 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge |
验证服务器自建证书
1 | //先导入证书 |
一个trust对象包括一组要验证的服务器证书以及一组用来验证trust的策略,也可以可选的包含一组其他证书(用来验证第一个证书)
上面的代码通过SecTrustSetAnchorCertificates
来额外设置了一个可信任的证书数组,当再次调用SecTrustEvaluate的时候,验证范围变成了SecTrustSetAnchorCertificates配置的证书,如果我们仍然想信任系统的证书,需要调用SecTrustSetAnchorCertificatesOnly方法,清空这个标示(设置为NO)这时候调用SecTrustEvaluate 的验证范围就是系统证书+SecTrustSetAnchorCertificates配置的证书,当其中有一个是可信的,那么SecTrustEvaluate会返回成功
无论我们验证的结果如何必须调用 completionHandler 并传入NSURLSessionAuthChallengeDisposition
以及一个NSURLCredential(可以为nil)
对象,NSURLSessionAuthChallengeDisposition有如下定义
- NSURLSessionAuthChallengeUseCredential 使用一个特定的NSURLCredential对象,可以是nil
- NSURLSessionAuthChallengePerformDefaultHandling 默认的操作,效果与没有实现delegate类似,不做任何验证
- NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消验证,请求失败
- NSURLSessionAuthChallengeRejectProtectionSpace 暂时不了解含义
系统验证证书api
1 | //为trust object 设置除了系统内置信任证书以外的信任证书 |
1 | //anchorCertificatesOnly 为yes 只信任SecTrustSetAnchorCertificates设置的证书 |
1 | //会根据上面两个方法设置的信任证书列表来验证服务端数字证书 |
修改验证策略
默认的验证策略是验证域名,要求请求域名与服务器证书的域名一致,如果我们服务器的证书需要对应多个域名的话,我们需要修改验证策略,让其忽略域名
1 | //获取验证策略 |
1 | //修改验证策略 |
AFNetWorking
AFNetworking主要使用AFSecurityPolicy
来实现HTTPS
AFSecurityPolicy分三种验证模式:
AFSSLPinningModeNone
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
AFSSLPinningModeCertificate
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端保存的是否一致。
AFSSLPinningModePublicKey
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
整个AFSecurityPolicy就是实现这这几种验证方式,剩下的就是实现细节了,详见源码。
1 | - (void)URLSession:(NSURLSession *)session |
AFSecurityPolicy
1 | - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust |