Https通信原理及Android中实用总结

2,260 阅读20分钟

一、背景

Http俨然已经成为互联网上最广泛使用的应用层协议,随着应用形态的不断演进,传统的Http在安全性上开始面临挑战,Http主要安全问题体现在:
1,信息内容透明传输。
2,通信对方的身份不可安全验证。
3,通信信息可能被篡改。

于是,结合SSL/TLS协议,形成的Https,被广泛使用。Https现在已经成为业内安全的Http协议的事实标准。

无论是Web前端,还是Android,或iOS客户端等终端,与服务端进行Https通信时,通信过程与原理都是相通的,面对需要解决的问题也大同小异。本文主要通过梳理Https通信过程,并总结其在Android中的实际应用,加深对Https的理解。


二、Https通信过程

Https涉及到的知识点很多,如果是完整的旧项目从Http迁移到Https,过程也会相对复杂。本文更多的从客户端的视角,去梳理与Https密切相关的知识体系。其中,加密算法、散列算法,数字证书等,都是非常重要的技术点。

2.1 对称加密

对称加密,对于加密和解密算法来说,加密和解密的密钥是相同的,如同同一把锁,加密密钥和解密密钥都是相同的一把钥匙。用密钥加密后,得到加密后的密文,随后可以直接用其进行解密,得到初始的原文。

对于通信的双方,通过对称加密,可以很方便的将通信内容进行加密,然后进行安全传输,能够有效的防止信息在中途被中间人获取到原文。即使信息中途有被截获的可能,但只要密钥没有泄露,信息本身还是安全的。

鉴于安全、方便、高效等特点,对称加密被广泛的使用在信息传输过程中。

2.2 非对称加密

对称加密存在最大的一个问题,在于如何将对称密钥安全的告知到对方。在一个客户端可以同时和多个服务端通信,一个服务端可以同时和多个客户端通信的现实环境下,这种关系是多对多的,而对称密钥又只能由当前通信的客户端和服务端所持有,因此,在实际通信过程中,对称密钥的协商成为现实中难点。

与对称加密不同的是,非对称加密同时具有公钥和私钥。如果用公钥进行加密,则只能通过对应的私钥去解密,如果用私钥进行加密,则只能通过对应的公钥去解密。 在非对称加密体系中,公约和私钥是一对活宝,缺少任何一方都是不可以的。私钥往往都主体对象自己保留,公钥则可以直接向外界公开。

公私玥的存在,使得非对称加密具有了相对对称加密明显的优点。
1,公钥直接向外界公开,外界任一主体通过公钥进行加密,可以直接向持有私钥的主体安全的发送加密后的密文,私钥主体拿到密文后,通过自身持有的私钥对应解密,获取明文,以此完成信息的安全传输过程,且无须面临对称加密中对称密钥的安全协商问题。

2,私钥主体通过私钥对信息进行加密,外界任一主体通过公钥去进行解密,如果可以成功解密,则能反向确定对应通信对方的身份,相当于完成了身份校验。以此,非对称加密具有很好的身份验证能力。

凡事有利有弊,非对称加密相比对称加密,因为本身功能更加强大,在算法设计上对对应的也更加复杂,在加解密效率上,远低于对称加密。

2.3 散列算法

散列算法,也称之为摘要算法或哈希算法,可以将任一数据对象压缩成数据摘要,对于同一散列算法,压缩后的数据摘要具有特定的长度和格式,以此形成数据的“指纹”。原始数据对象的任一细小改动,都可能使得新的“数字指纹”有着很大不同。

往往通过散列算法,可以判断两个数据对象是否相同,因为相同的数据对象,通过散列算法形成的“数字指纹”必定是相同的。但是相同的“数字指纹”,对应的原始数据对象不一定是相同的,因为可能存在散列冲突问题。

散列算法,在现实中就有广泛的使用场景,例如最常用的对两个文件对象进行MD5,判断其内容是否相同。

严格意义上来说,散列算法与加密算法(对称加密或非对称加密)是有着本质区别的,加密算法,对应的是可以解密的,目的是进行数据加密后的安全存储或传输,是可以通过密钥得到原文的,是可逆的过程。而散列算法,本质上“数字指纹”的范畴,通过散列算法形成的“数字签名”,直接在算法层面是不能得到原文的,是不可逆的。当然,通过彩虹表或数据字典这种形式的所谓解密,本质上只是暴力破解的过程。

2.4 Https通信流程

Https = Http + SSL/TLS = Http + 内容加密 + 身份验证 + 数据完整性保护。当前,TLS主要使用的版本是1.2,按照RFC官网,Https通信过程示意如下:

完整的具体过程可以参照下图(来源:wiki):

从整体上看,Https通信过程包含了秘钥协商和加密通信两个阶段。

2.4.1 秘钥协商

秘钥协商是整个Https通信过程中的重点部分。此处的秘钥,指的是客户端与服务端协商后续用于信息加密的对称秘钥。协商的主要过程包括:
1,客户端向服务端发送ClientHello消息,其中包含客户端随机生成的随机数RNc,客户端的协议版本信息、加密套件信息等。
2,服务端接收消息,并向客户端发送ServerHello消息,其中包括服务端随机生成的随机数RNs,服务端协议版本信息,加密套件等信息。
3,服务端向客户端发送包含了服务端公钥的证书。
4,服务端向客户端请求包含了客户端公钥的证书。
5,客户端核验服务端证书,并向服务端发送包含了客户端公钥的客户端证书。
6,服务端核验客户端证书。
7,客户端生成随机数PMS,并通过服务端公钥将PMS进行加密,发送给服务端。
8,客户端和服务端通过同样的算法,以及RNc,RNs,PMS,分别生成对称加密秘钥,因为算法和参数都是相同的,因此,两端生成的对称秘钥也是相同的。

以此完成两端对称秘钥的协商过程。

2.4.2 加密通信

秘钥协商完成后,SSL握手过程结束。后续的通信过程都基于协商了的对称秘钥进行加密,在进行传输。这个过程与一般意义上的加密通信过程没有差别。

秘钥协商的最终目的,是在客户端和服务端安全的同时生成用于后续加密通信过程的对称秘钥。这也正是Https所谓的安全的根本。

2.5 CA及数字证书

秘钥协商过程中,客户端和服务端如何安全的交换彼此的非对称加密的公钥成为重点。因为一旦安全的获知对方的公钥后,就可以通过对方公钥进行加密,进一步完成后续对称秘钥的协商。在获得对方公钥时,如何对通信对方身份进行核验,以确保此公钥就是真实的通信对方的公钥?

公钥不可能直接通过网络进行传输,否则依然存在信息被中间窃取和篡改的可能,进一步可能引发中间人攻击。因此,在获得对方公钥的同时,还能有一套机制去核验对方的身份,以及对方公钥的准确性,是一大难点。

直接通过网络方式,将永远是一个鸡生蛋和蛋生鸡的问题,安全性永远没法得到保障。由此,通过基于CA的方式,颁发数字证书,来对通信方的身份进行“担保”,对数据完整性提供核验,被设计出来。

通过信任CA,由此信任CA颁发的数字证书,
由此信任通信方并获取到对方的公钥。

对应的,现在存在两个问题:
1,CA本身的身份,如何信任?
2,信任CA后,如何校验数字证书以确认通信对方的身份并获取到对应的公钥?

实际应用中,对CA身份的信任,提供了两种途径。一种是直接基于系统的集成,将全球主要的CA本身对自己签发的数字证书集成到系统中。如Mac电脑系统或Android手机等。另一种是基于人为的手动授信,如通过浏览器下载第三方的CA数字证书,并设置对其进行信任。无论是哪种方式,系统将会对集成了的CA数字证书信任,并能获取到对应的CA公钥。

接下来以客户端校验服务端证书为例,阐述完整的身份核验和公钥获取过程。

管理员向CA申请数字证书,其中包括服务端公钥,域名,证书有效期,组织机构,地域,证书序列号,颁发机构等信息。这些信息正文,通过散列算法H,形成信息摘要,CA对生成的信息摘要,通过CA自身的非对称加密的私钥进行加密,得到密文,此密文称之为数字签名(CA对信息摘要进行了签名),信息正文、数字签名,放在一起,形成数字证书。

数字证书上,包括两大内容:
1,信息正文
2,数字签名。

服务端将证书发送给客户端后,客户端首先拿到证书的CA信息,与系统中已经授信的证书CA进行比较,如果发现存在同样的CA,则使用CA的公钥对其进行解密。否则将尝试基于证书链的形式对服务端证书CA的身份进行验证,通过后再基于CA公钥进行解密。否则,服务端证书校验失败。

CA公钥解密数字证书上的数字签名后,可以得到消息摘要,再通过对信息正文进行同样的散列算法,也得到消息摘要,将两个消息摘要进行对比,以完成数据完整性的校验。同时,服务端身份以及数字证书上的服务端公钥也得以确认。


三、Https抓包原理及Charles实践

3.1 中间人攻击

Https抓包,本质上是基于中间人攻击。安装好抓包软件,开启抓包服务,将客户端(手机或浏览器等)配置好相应的代理服务器地址及端口。此时,客户端的Http/Https请求将通过抓包服务中转,对Http/Https通信服务端而言,抓包服务充当了客户端角色,对源客户端而言,抓包服务充当了服务端角色。因此,抓包服务形成事实上的代理,且同时具有正向代理和反向代理的职能。

通信经过抓包服务,但对于Https请求,由于报文主体是加密的,此时虽然能抓到包,但看到报文主题是乱码形式。因为此时抓包服务无法对报文主体进行透明解密,也无法达到可以篡改的效果。此时,针对报文主体,更多的扮演的是一个中间代理转发通信的角色。因为此时,客户端对通信服务端的身份依然既有有效的核验过程。

在使用抓包软件时,都会要求客户端安装一个证书并设置信任。这个证书,就是抓包服务所对应的证书,客户端信任此证书后,相当于完全信任了抓包服务。此时,抓包服务具备了完整意义上的通信中转、分发、信息透明和篡改等功能,达到了完整意义上中间人攻击的效果。源客户端和源服务端实际上将不再是和对方通信,只是他们以为,通信方还是彼此。

信任了抓包服务的证书,此后,源客户端实际上,是在和抓包服务进行通信,对服务端的身份和信息完整性校验,也是基于抓包服务的公钥。源客户端的请求,对抓包服务将是完全透明的,抓包服务可以篡改此请求,并扮演成源客户端角色,和源服务端通信,因为抓包服务具有源服务端的公钥和协商后的对称加密秘钥,因此,服务端返回的结果,对抓包服务是透明的,抓包服务可以对其进行篡改,并返回给源客户端。这也是很多抓包工具,不但具有对通信内容的透明显示,也还具有额外的调试功能,如通过对请求参数,或返回结果等的修改,达到想要的调试目的等。

主流的Http/Https抓包工具有Fiddler、Charles等。Mac上用的较多的一般是Charles。下面主要总结下Charles实践部分。

3.2 Charles破解

当前官方版本的Charles 是收费软件,有30天的免费试用时间,即使试用期过后,未付费的用户仍然可以继续使用,但是每次使用时间不能超过 30 分钟,并且启动时将会有 10 秒种的延时。实际使用过程中有点不太方便。可能用着用着,需要你重启。

网上提供了很多Charles破解的资料,在此只是做一个总结,平时使用请支持正版。

最新Charles版本是v4.2.8。

3.2.1 Charles破解工具

Charles在线破解工具:
www.zzzmode.com/mytools/cha…
对应的还有一个历史版本的破解说明和Github项目地址:
blog.zzzmode.com/2017/05/16/…
github.com/8enet/Charl…

RegisterName输入自己喜欢的注册名,然后选择自己本机已经安装的Charles版本,点击“生成”,下载对应生成的charles.jar文件。

覆盖本机对应Charles安装文件。/Applications/Charles.app/Contents/Java/charles.jar 重启Charles,发现显示“Registered to: corn”,且已经正式激活。

3.2.2 Charles破解原理

Charles对于是否破解的判断,是写在charles.jar文件中的,找到混淆后的对应class文件,将对应破解判断的逻辑,通过修改字节码的方式强制改成已经注册逻辑,然后重新生成charles.jar文件。

3.2.3 Beyond Compare比较.class文件

具体可以通过Beyond Compare对比两个charles.jar文件,看看具体差异在什么地方。

Beyond Compare默认不支持.class格式的文件比较。因此,默认情况下,如果直接比较,可以发现唯一差异的文件为:qHTb.class,但文件中的内容是识别不了的,以类似乱码形式展示。

Beyond Compare默认支持的文件格式可以通过Beyond Compare >> 文件格式...查看。默认不支持.class格式的文件,我们可以添加其对.class格式的文件支持。

网址:www.scootersoftware.com/download.ph… 上提供了其他的文件格式,我们可以对应搜索并下载后,通过工具 >> 导入配置...给配置进来,以此支持此类文件文件。但.class文件格式只提供了Windows平台的支持,Mac下没有提供。

不过,我们可以通过配置文件格式为外部程序转换的方式进行额外的格式支持。上述Windows平台中的.class文件支持中,具体可以看到如下描述:

Java class to source8-Feb-2019 *.class 
- Compares Java class files decompiled to source. 
- - Uses Java decompiler, Jad: http://varaneckas.com/jad

因此,我们也可以通过直接配置jad的方式,直接对比.class文件。

a, http://varaneckas.com/jad对应的jad版本,解压后放置到系统合适目录下,如/Users/corn/Research/jad158g/
b,Beyond Compare >> 文件格式... >> 新增格式化文件General中配置*.class,Conversion中勾选上外部程序,并配置上对应的外部程序命令:/Users/corn/Research/jad158g/jad -p %s >%t

c,再次对比qHTb.class,发现已经可以直接对比查看了(如果还不行试着选择导航中的Format >> class文件)。

我们发现,破解的charles.jar,只是修改了qHTb.class字节码中的DdNM()方法,直接返回了true,此为校验是否注册的方法。另一个方法是gbef(),此为获取注册名的方法。

由此,通过强制修改字节码的方式将对应逻辑改成已经注册,完成破解。

3.3 Charles抓包注意点

Charles抓包,网上实际上有很多文章,都有专门阐述,此处主要总结下抓包过程中,需要注意的地方:
1,源客户端,需要安装后Charels对应的证书,并对其信任。具体过程参考菜单Help >> SSL Proxying中。
2,需要具体配置好Proxy Setting,且SSL Proxy Setting也要配置好,这一点经常容易忘。如果想所有的包,可以直接配置*。但需要特别注意的是,如果勾选上Proxy >> MacOS Proxy后,设置的是系统代理模式,可能会对系统的其他软件的使用造成影响,例如Android Studio。因为此时,AS也很可能走对应的代理(因为可能有缓存)。当然,如果需要抓包国外的一些被墙了的域名地址,也可以通过设置External Proxy Settting ...形式走外部的其他的代理。
3,通过Start Recording,开启抓包。抓包过程中可以进行延迟、断点、rewriteMap Remote/Local等各种调试技巧。断点不一定有效,因为实际中,发现往往会延迟。rewrite、Map实际中,非常有用,例如和服务端联调等。
4,有些情况下,源客户端是可以禁掉抓包服务的,例如配置上不使用代理等。此时,Charles等抓包服务将会失效。


四、Android防抓包原理及总结

Android开发中,通过抓包分析竞品App的通信信息,是很常见的。但往往也发现,有些App,是抓不了包的。为了自身App的安全性等考虑,正式对外发布的安装包,有必要做防抓包处理。

抓包的本质,是中间人攻击。针对中间人攻击的原理,在其中关键环节,打破抓包工具成为完整意义上中间人的条件,便可以实现Android App的防抓包防护。

1,源客户端忽略代理设置。
Android项目中,可以通过判断当前是否在使用代理,通过忽略掉代理设置,实现请求的直接连接。
一般的对是否使用代理的判断写法如下:

/**
 * 判断是否是在代理环境下
 *
 * @return
 */
private boolean isUsingProxy() {
    // 是否大于等于14
    final boolean isIceOrUpper = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;

    if (isIceOrUpper) {
        proxyAddress = System.getProperty("http.proxyHost");
        String portStr = System.getProperty("http.proxyPort");
        proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
    } else {
        proxyAddress = Proxy.getHost(BaseApplication.context);
        proxyPort = Proxy.getPort(BaseApplication.context);
    }

    return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}

OkHttp直接提供了忽略代理设置的接口。如果发现正在使用代理,可以设置忽略代理。

OkHttpClient.Builder builder = new OkHttpClient.Builder();
...
builder.proxy(Proxy.NO_PROXY);
....

2,源客户端设备提高证书信任级别。
Android 7.0开始,将网络安全配置中的证书由原来的“系统级证书”和“用户级证书”改成了“系统级证书”。这也就意味着,“用户级证书”默认将不被信任,即使用户自己,已经设置了对其信任。因此,我们会发现,默认情况下,在Android 7.0及以上手机上,是抓不了包的。
抓包代理显示的信息为:

源客户端显示的信息为:

Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:661)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:539)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:605)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:495)
        at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:418)
        at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:339)
        at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
        at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)
        at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:197)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:399)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.SslWrapper.doHandshake(SslWrapper.java:374)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:217)
        ....

同时,Android对开发者提供了网络安全配置入口,如果需要抓包,只需要将“用户级证书”加入到对应配置项即可。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="user" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

<certificates src="user" />是配置的重点,表示“用户级证书”可被信任。
开发过程中,或内部的测试包,可能需要抓包,因此,对内环境开启此配置项是很有必要的。但对外的正式包中,应该保持系统原本配置,以提升抓包防护的安全级别。

针对不同场景下的网络安全配置需求,官方文档也给出了解决方法,例如提供了android:debuggable为true情况下的专门配置。但实际App开发中,往往此开关都直接是false配置的,解决方法也很简单,可以针对App的buildTypesproductFlavors甚至变体去单独配置network-security-config即可。如针对对内的dev环境下配置上<certificates src="user" />,以使得可以抓包,对外环境下去掉此配置。

3,源客户端加强对服务端的信任校验。 源客户端加强对服务端的信任校验,实际中根据具体的需求场景,服务端证书的配置方式等,可以有多种方式。例如源客户端内置上服务端证书,或者对服务域名进行安全校验,又或者对证书名称进一步校验等。一旦发现不符合预期的证书信息项,则终止对应请求。

下面简单演示对服务端信息判断,一旦是抓包服务信息,则终止掉请求。

public class ServerCertificateSecurityVerifier implements HostnameVerifier {
    ....
    
    private static final String[] DEFAULT_INSECURITY_CA_ISSUER_KEYWORDS = new String[]{
            // fiddler
            "fiddler.com",
            // fiddler
            "fiddler2.com",
            // charles
            "charlesproxy.com",
            // whistle
            "wproxy.org",
            // Android Packet Capture
            "Packet Capture CA Certificate",
            // mitmproxy
            "mitmproxy",
            // debug proxy
            "Debug Proxy"
    };
    
    @Override
    public boolean verify(String hostname, SSLSession session) {
        // 不允许抓包条件下,判断正式信息是否包含主流的抓包代理,如果,校验失败,否则,走默认流程
        try {
            for (X509Certificate x509Certificate : session.getPeerCertificateChain()) {

                if (x509Certificate.getIssuerDN() == null || StringUtil.isEmpty(x509Certificate.getIssuerDN().getName())) {
                    continue;
                }

                String caIssuerCompleteName = x509Certificate.getIssuerDN().getName();
                for (String insecurityCAIssuerKey : DEFAULT_INSECURITY_CA_ISSUER_KEYWORDS) {
                    // 证书信息中有抓包工具的证书信息
                    if (Pattern.compile(Pattern.quote(insecurityCAIssuerKey), Pattern.CASE_INSENSITIVE)
                            .matcher(caIssuerCompleteName).find()) {
                        Log.w(, TAG, "ServerCertificate error... - " + caIssuerCompleteName);
                        return false;
                    }
                }

            }

        } catch (Exception e) {
            Log.e(TAG, e);
        }
        return verifyByOkHttpDefault(hostname, session);
    }

五、结语

网络请求的安全性问题现在愈发得到重视,Google和Apple也明确要求在其应用市场上上架的App都必须遵循Https安全标准。Google Play渠道上架的条款中,也明确规定,对于逻辑中可能存在的中间人攻击行为,需要做进一步的安全处理,否则不能上架成功。从Android 7.0开始提升系统默认情况下只支持“系统级证书”等行为上看,Https的安全要求和标准也会不断提高。平时Android App开发过程中,与服务端通信过程中涉及到的安全性问题,还有很多实际的场景,基于Https基础上,需要做进一步的加密处理或安全校验,以尽可能的提高App安全防护级别。





参考网址:
tools.ietf.org/html/rfc524…
developer.android.com/training/ar…
codelabs.developers.google.com/codelabs/an… mp.weixin.qq.com/s/3M0CqFQP2…
zh.wikipedia.org/wiki/傳輸層安全性…
zh.wikipedia.org/wiki/超文本传输安…