Alamofire 学习--安全认证

1,528 阅读7分钟

一、HTTPS

在学习 Alamofire 的安全认证之前,我们先来了解下 HTTPS

1、为什么要用 HTTPS

HTTP 协议中有可能存在信息窃听或者身份伪装等安全问题,使用 HTTPS 通信机制可以有效地防止这些问题。 HTTP 的不足:

  • 通信使用明文(不加密),内容可能会被窃听。
  • 不验证通信方的身份,因此有可能遭遇伪装
  • 我发证明报文的完整性,所以有可能已遭篡改 像我们平时常用的抓包软件 Wireshark、Charles 等都可以轻松抓取到 HTTP 的请求和响应数据。

HTTP 协议中没有加密机制,但通过 SSLSecure Socket Layer,安全套接层)或 TLSTransport Layer Security ,安全层传输协议)的组合使用,加密 HTTP 的通信内容。 与 SSL 组合使用的 HTTP 被称为 HTTPSHTTP Secure,超文本传输安全协议)或 HTTP over SSL

HTTPS = HTTP + 加密 + 认证 + 完整性保护

HTTPS 并非是应用层的一种新协议,只是 HTTP 通信接口部分用 SSLTLS 协议代替而已。通常 HTTP 直接跟 TCP 通信,当使用 SSL 时,则变成先和 SSL 通信,再由 SSLTCP 通信了。简言之,HTTPS 就是身披 SSL 协议这层外壳的 HTTP

2、加密技术

我们常用的加密方式有共享密钥加密(对称加密)和公开密钥加密(非对称加密)两种。

💡:在现代汉语里,“钥”读yào的,只出现在【钥匙】这个词里,“钥”居词首。其他都读yuè,且都出现在词尾。

1、共享秘钥加密

加密和解密同用一个秘钥的方式称为共享秘钥加密Common key crypto system),也称为对称秘钥加密

💡:这个很好理解,举个生活中常见的例子,你用钥匙把钱放保险柜里了,把钥匙给你媳妇了,你媳妇只有通过这把钥匙才能打开保险柜拿到钱。(钥匙就是秘钥,钱就是信息原文,装了钱的保险柜就是密文)

  • 秘钥发送问题: 以共享秘钥方式加密时必须将秘钥也发给对方,但在互联网上转发秘钥时就有被窃听的风险,不发送对方就不能解密,如果秘钥都安全送达了,那数据也应该能安全送达了。另外还得设法安全的保管接收到的秘钥。

2、公开秘钥加密

公开秘钥加密很好地解决了共享秘钥加密的困难。 它使用一堆非对称的秘钥,一把叫做私有密钥(private key),另一把公开密钥(public key),顾名思义,私钥不能让其他任何人知道,而公钥则可以随意发布。

使用公开秘钥加密方式,发送密文的一方使用对方的公开密钥进行加密,对方收到被加密的信息后再用自己的私钥进行解密。这样就不用发送私钥了,也就不用担心被窃听盗走私钥。如果想仅仅根据密文和公钥就恢复信息原文就目前的技术来看是不太现实的。

💡我想个例子帮助理解:腾讯发布了微信 app,所有人都可下载注册登录,但只有腾讯有办法查到你的密码数据。别人就算拿了到你已经把微信登陆成功的手机,他在 app 里也找不到你的密码(用手机验证重置密码也得不到旧密码数据)。这里微信软件注册登录功能就是公钥,密码数据就是信息原文,登录以后的微信就是密文,腾讯查询你密码数据的能力就是秘钥

3、HTTPS 混合加密机制

SSL 采用的是公开密钥加密(Public-key cryptography),属于非对称加密。HTTPS 采用混合加密机制,就是共享秘钥加密和公开密钥加密两者并用的混合加密机制。

为什么不全用公开密钥加密?因为与共享秘钥加密相比,处理起来更复杂、处理速度慢。所以 HTTPS 充分利用两者优势,在交换秘钥环节使用公开密钥加密方式,之后的简历通信交换报文阶段则用时共享秘钥加密方式。

3、证明公钥正确性的证书

公开密钥加密方式仍然有一些问题,那就是没办法证明公钥本身是不是真的公钥,有可能在公钥传输中,真正的公钥已经被攻击者替换掉了。这时就需要使用数字证书认证机构(CA,Certificate Authority)和其相关机关颁发的公开密钥证书。 数字证书认证机构的业务流程:

数字证书的生成是分层级的,下一级的证书需要其上一级证书的私钥签名。所以后者是前者的证书颁发者,也就是说上一级证书的主题名是其下一级证书的签发者名。 例如打开我们的 Safari 浏览器,在简书的网站可以看到网址前有一个🔐的标志,点一下选择显示证书。 发现简书的根证书是 DigiCert Global Root CA

二级证书是 DigiCert SHA2 Secure Server CA

最后的三级证书是 **.jianshu.com

二、Alamofire 中的安全认证

Alamofire 请求的安全认证配置有这六种模式:

public enum ServerTrustPolicy {
    case performDefaultEvaluation(validateHost: Bool)
    case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
    case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
    case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
    case disableEvaluation
    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
  • .performDefaultEvaluation 默认策略,只有合法证书才能通过验证
  • .performRevokedEvaluation 对注销证书做的一种额外设置
  • .pinCertificates 证书验证模式,代表客户端会将服务器返回的证书和本地保存的证书中的 所有内容 全部进行校验,如果正确,才继续执行。
  • .pinPublicKeys 公钥验证模式,代表客户端会将服务器返回的证书和本地保存的证书中的 PublicKey部分 进行校验,如果正确,才继续执行。
  • .disableEvaluation 该选项下验证一直都是通过的,无条件信任。
  • .customEvaluation 自定义验证,需要返回一个布尔类型的结果。

最常用的有这三种: .pinCertificates 证书验证模式、.pinPublicKeys 公钥验证模式和 .disableEvaluation 不验证模式

1、证书验证模式:

pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
  • 参数1:certificates代表的是证书
  • 参数2:validateCertificateChain代表是否验证证书链
  • 参数3:validateHost代表是否验证子地址

可以看到 Alamofire 非常人性化,在 ServerTrustPolicy 枚举中还给我们提供了下面的方法,帮我们遍历查找出了项目主 bundle 中的证书:

public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
    var certificates: [SecCertificate] = []
    let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
        bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
        }.joined())
    
    for path in paths {
        if
            let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
            let certificate = SecCertificateCreateWithData(nil, certificateData)
        {
            certificates.append(certificate)
        }
    }
    return certificates
}

certificates 传参的时候直接传 ServerTrustPolicy.certificates() 即可,如果你的证书是放在其他 bundle 中,也可以传参进去指定 bundle

最后使用证书模式验证完的整请求如下:

let serverTrustPlolicies:[String: ServerTrustPolicy] = [
    hostUrl: .pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true)
]
let sessionManger = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPlolicies))

sessionManager?.request(urlString).response { (defaultResponse) in
    print(defaultResponse)
}

2、公钥验证模式:

如果采用公钥验证模式,则使用 .pinPublicKeys 这个枚举值,第一个参数传公钥,其他参数和证书验证模式一样:

case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)

第一个参数直接传递 Alamofire 提供给我们的 ServerTrustPolicy.publicKeys() 方法即可。公钥验证模式的好处是,只要公钥不变,就可以一直使用,不用更新证书,不用担心证书过期了。

3、不验证模式:

如果采用公钥验证模式,则使用 .disableEvaluation,这样就不管有没有证书,证书有没有效都采用非安全方式访问了。

以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:

1、上野 宣 的《图解https》

2、作者StanOz的《iOS 中对 HTTPS 证书链的验证》

转载请备注原文出处,不得用于商业传播——凡几多