HTTPS/SSL/TSL 是如何保证网络安全的(原理)

1,158 阅读7分钟

HTTPS 我们都已经很熟悉,是在原来的 HTTP 的基础上通过 SSL(TSL) 加密,用来提供对对方服务器/客户端的身份认证、隐私性和完整性。

HTTP 的问题

HTTP 最大的问题是数据明文传输,很容易被截取甚至篡改,且客户端无法得知。我个人印象最深刻的是以前用联通的卡经常会被劫持在某些 HTTP 页面上添加广告。

bVbClUf.png

HTTPS

HTTPS 相比 HTTP最大的不同就是多了一层 SSL (Secure Sockets Layer 安全套接层)或 TLS (Transport Layer Security 安全传输层协议)。

可以理解为 HTTPS = HTTP + SSL/TSL ,主要增加了身份确认、数据加密的功能。

需要加密的话就涉及到了加密算法,加密算法一般分为两种对称加密非对称加密

对称加密:

对称加密有一把密钥,这把密钥可以加密明文,也可以解密加密后的密文,常见的对称加密算法有AES、DES、RC4,目前最常用的是AES。

  • 优点:算法公开、计算量小、加密速度快、加密效率高,适合加密比较大的数据。
  • 缺点:密钥无法安全的传输容易泄露、每次会话都需要不同的密钥。
非对称加密:

非对称加密有两把密钥,分别是公钥和私钥,公钥加密的密文只有相对应的私钥才能解密,私钥加密的内容也只有相对应的公钥才能解密,其中公钥是公开的,私钥是自己保存,不能公开,常见的非对称加密算法有RSA和ECC(椭圆曲线算法)。

  • 优点:算法公开,加密和解密使用不同的钥匙,私钥不需要通过网络进行传输,安全性很高。

  • 缺点:计算量比较大,加密和解密速度相比对称加密慢很多。

最终 SSL 结合了这两种加密算法的优点,通过非对称加密来验证身份及协商对称加密的密钥,验证成功之后便使用对称加密对数据加密。

数字证书及验证

在验证身份阶段 SSL 使用了非对称加密,客户端会收到服务器端传来数字证书,数字证书包含的信息包括 : 证书发布机构,证书有效期,公钥,证书所有者,签名使用的算法以及签名...

数字证书通过 CA 机构使用私钥签发后,客户端通过公钥解密后再对信息通过 hash 算法后,验证指纹和证书里的指纹是否一致。

CA

CA(Certificate Authority)是数字证书认证中心的简称,是指发放、管理、废除数字证书的机构。

由于实际上任何人都可以自己签发证书,我们并没有办法避免中间人攻击,这时需要引入受信任的证书发放机构来为认证的服务器签发数字证书,称为 CA。

证书具有树状的信任链,根证书有根 CA 机构颁发,为了避免根CA的证书出问题或私钥泄露,根 CA 机构会为一些二级/三级 CA 机构发放 CA 证书并内置在系统/浏览器中,一般服务器厂商的数字证书由这些二级/三级 CA 机构签发。

查看 B 站证书信息:

基本流程:

  1. 客户端发起一个 HTTP 请求,告诉服务器自己支持哪些 hash 算法。
  2. 服务端把自己的信息以数字证书的形式返回给客户端(证书内容有密钥公钥,网站地址,证书颁发机构,失效日期等)。 ...
  3. 验证证书的合法性 ...
  4. 生成随机密码(RSA签名) ...
  5. 生成对称加密算法

单向认证流程

一般常见的 HTTPS 用法都启用的单向认证流程,会信任所有系统中证书,这样的话:

一方面用户可能被安装自签名的证书;另一方面,CA 也可能出现受骗的情况。这样可能会被中间人攻击( MITM attack)。

常见的就是被 Charles 等抓包和篡改数据。要处理这样的问题,就需要升级认证的限制。

中间人攻击过程:
  1. 本地请求被劫持(如DNS劫持等),所有请求均发送到中间人的服务器
  2. 中间人服务器返回中间人自己的证书
  3. 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输
  4. 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密
  5. 中间人以客户端的请求内容再向正规网站发起请求
  6. 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据
  7. 中间人凭借与正规网站建立的对称加密算法对内容进行解密
  8. 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输
  9. 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密

如果信任了自签名的证书,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。

SSL-Pinning

升级 HTTPS 的安全限制的一种方案是对 APP 进行 SSL-Pinnig,原理就是把信任所有系统中 CA 颁发的证书的范围缩小到一个指定的子集,做法则是需要访问的站点的证书签名内置到 APP 里,这样在 HTTPS 通讯时会验证服务器的签名,一致时才继续,被中间人替换后证书则不会被信任。

但除非对安全性要求很高, Android 官方文档中也是不推荐这样做的,主要原因是服务器更换证书的话,App就无法访问。而且现在证书最长有效期为1年,所以每年一定需要更换。

Android

在 Android 中可以通过修改系统的网络安全配置来达到 SSL-Pinning 效果,编写一个如下的network_security_config.xml

  <?xml version="1.0" encoding="utf-8"?>
    <manifest ... >
        <application android:networkSecurityConfig="@xml/network_security_config"
                        ... >
            ...
        </application>
    </manifest>
  <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config>
            <domain includeSubdomains="true">secure.example.com</domain>
            <domain includeSubdomains="true">cdn.example.com</domain>
            <trust-anchors>
                <certificates src="@raw/trusted_roots"/>
            </trust-anchors>
        </domain-config>
    </network-security-config>

或者直接指定数字签名

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config>
            <domain includeSubdomains="true">example.com</domain>
            <pin-set expiration="2018-01-01">
                <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
                <!-- backup pin -->
                <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
            </pin-set>
        </domain-config>
    </network-security-config>
    

这样,只有指定的域名和 CA/签名 会被接受。

不过实际上,这个配置并不是100%保证所有流量都会受到限制,所以需要保证100%严格限制的话,可以在 API 请求代码中去配置。

//OkHttp
val client = OkHttpClient().newBuilder()
            .certificatePinner(
                CertificatePinner.Builder()
                    .add("www.pin.com", "sha1orsha256")
                    .build()
            ).build()

iOS

iOS 没有类似 Android 的网络安全配置,只能在代码中修改。一般来说在 HttpClient 中有类似的接口, WKWebView 的话,可通过 webView(_:didReceive:completionHandler:) 检查服务器证书情况。

optional func webView(_ webView: WKWebView, 
           didReceive challenge: URLAuthenticationChallenge, 
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

Flutter

Flutter 中,Android 端依然参考 Android 的方法; iOS则直接在 HTTPClient 中配置就可以了,WebView 如果用了 InAppWebView插件,那么在 onReceivedServerTrustAuthRequest 方法中获取证书信息进行处理。

    onReceivedServerTrustAuthRequest: (c,challenge)async{
      LogUtil.logV("onReceivedServerTrustAuthRequest:${ch.protectionSpace.sslCertificate?.x509Certificate}");
      return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.CANCEL);
    },

突破 SSL-Pinning

App 配置了 SSL-Pinning 其实已经很安全了,但系统权限较高的手机依然可以通过类似 Hook 的技术来绕开,例如 Android 上可以用 Xposed + JustTrustMe 。

所以检测 Root / 越狱也是必要的。

双向认证流程

目前 SSL 证书认证分为单向认证和双向认证,单向认证只要求站点部署了 SSL证书,任何用户都可以去访问,只是服务端提供了身份认证。而双向认证则是需要是服务端需要客户端提供身份认证,只能是服务端允许的客户能去访问,安全性更高。

参考:

HTTPS

通过 HTTPS 和 SSL 确保安全