Android HTTPS、TLS版本支持相關(guān)解決方案(轉(zhuǎn)發(fā))

原作:https://blog.csdn.net/s003603u/article/details/53907910
該文章內(nèi)容只是轉(zhuǎn)發(fā)

前言

在互聯(lián)網(wǎng)安全通信方式上,目前用的最多的就是https配合ssl和數(shù)字證書來保證傳輸和認證安全

簡介

  • 結(jié)合okhttp實現(xiàn)https訪問,并解決其中遇到的問題
  • okhttp默認情況下是支持https協(xié)議的,不過要注意的是,支持https的網(wǎng)站如果是CA機構(gòu)頒發(fā)的證書,默認情況下是可以信任的。
  • 使用Charles進行https抓包

HTTPS相關(guān)

名詞解釋

  • https:在http(超文本傳輸協(xié)議)基礎(chǔ)上提出的一種安全的http協(xié)議,因此可以稱為安全的超文本傳輸協(xié)議。http協(xié)議直接放置在TCP協(xié)議之上,而https提出在http和TCP中間加上一層加密層。從發(fā)送端看,這一層負責(zé)把http的內(nèi)容加密后送到下層的TCP,從接收方看,這一層負責(zé)將TCP送來的數(shù)據(jù)解密還原成http的內(nèi)容。
  • SSL(Secure Socket Layer):是Netscape公司設(shè)計的主要用于WEB的安全傳輸協(xié)議。從名字就可以看出它在https協(xié)議棧中負責(zé)實現(xiàn)上面提到的加密層。因此,一個https協(xié)議棧大致是這樣的:
這里寫圖片描述
  • 補充:IP(網(wǎng)絡(luò)層)、TCP(傳輸層),HTTP(應(yīng)用層),SSL處于TCP和HTTP之間
  • 數(shù)字證書:一種文件的名稱,好比一個機構(gòu)或人的簽名,能夠證明這個機構(gòu)或人的真實性。其中包含的信息,用于實現(xiàn)上述功能。
  • 加密和認證:加密是指通信雙方為了防止敏感信息在信道上被第三方竊聽而泄漏,將明文通過加密變成密文,如果第三方無法解密的話,就算他獲得密文也無能為力;認證是指通信雙方為了確認對方是值得信任的消息發(fā)送或接受方,而不是使用假身份的騙子,采取的確認身份的方式。只有同時進行了加密和認真才能保證通信的安全,因此在SSL通信協(xié)議中這兩者都很重要。

因此,這三者的關(guān)系已經(jīng)十分清楚了:https依賴一種實現(xiàn)方式,目前通用的是SSL,數(shù)字證書是支持這種安全通信的文件。另外有SSL衍生出TLS和WTLS,前者是IEFT將SSL標(biāo)準(zhǔn)化之后產(chǎn)生的(TLS1.0),與SSL差別很小,后者是用于無線環(huán)境下的TSL。

圖解HTTPS協(xié)議加密解密全過程

我們都知道HTTPS能夠加密信息,以免敏感信息被第三方獲取。所以很多銀行網(wǎng)站或電子郵箱等等安全級別較高的服務(wù)都會采用HTTPS協(xié)議。

HTTPS其實是由兩部分組成:HTTP + SSL / TLS,也就是在HTTP上又加了一層處理加密信息的模塊。服務(wù)端和客戶端的信息傳輸都會通過TLS進行加密,所以傳輸?shù)臄?shù)據(jù)都是加密后的數(shù)據(jù)。具體是如何進行加密,解密,驗證的,且看下圖。


這里寫圖片描述
  • 客戶端發(fā)起HTTPS請求
    這個沒什么好說的,就是用戶在瀏覽器里輸入一個https網(wǎng)址,然后連接到server的443端口。
  • 服務(wù)端的配置
    采用HTTPS協(xié)議的服務(wù)器必須要有一套數(shù)字證書,可以自己制作,也可以向組織申請。區(qū)別就是自己頒發(fā)的證書需要客戶端驗證通過,才可以繼續(xù)訪問,而使用受信任的公司申請的證書則不會彈出提示頁面(startssl就是個不錯的選擇,有1年的免費服務(wù))。這套證書其實就是一對公鑰和私鑰。如果對公鑰和私鑰不太理解,可以想象成一把鑰匙和一個鎖頭,只是全世界只有你一個人有這把鑰匙,你可以把鎖頭給別人,別人可以用這個鎖把重要的東西鎖起來,然后發(fā)給你,因為只有你一個人有這把鑰匙,所以只有你才能看到被這把鎖鎖起來的東西。
  • 傳送證書
    這個證書其實就是公鑰,只是包含了很多信息,如證書的頒發(fā)機構(gòu),過期時間等等。
  • 客戶端解析證書
    這部分工作是有客戶端的TLS來完成的,首先會驗證公鑰是否有效,比如頒發(fā)機構(gòu),過期時間等等,如果發(fā)現(xiàn)異常,則會彈出一個警告框,提示證書存在問題。如果證書沒有問題,那么就生成一個隨機值。然后用證書對該隨機值進行加密。就好像上面說的,把隨機值用鎖頭鎖起來,這樣除非有鑰匙,不然看不到被鎖住的內(nèi)容。
  • 傳送加密信息
    這部分傳送的是用證書加密后的隨機值,目的就是讓服務(wù)端得到這個隨機值,以后客戶端和服務(wù)端的通信就可以通過這個隨機值來進行加密解密了。
  • 服務(wù)端解密信息
    服務(wù)端用私鑰解密后,得到了客戶端傳過來的隨機值(私鑰),然后把內(nèi)容通過該值進行對稱加密。所謂對稱加密就是,將信息和私鑰通過某種算法混合在一起,這樣除非知道私鑰,不然無法獲取內(nèi)容,而正好客戶端和服務(wù)端都知道這個私鑰,所以只要加密算法夠彪悍,私鑰夠復(fù)雜,數(shù)據(jù)就夠安全。
  • 傳輸加密后的信息
    這部分信息是服務(wù)端用私鑰加密后的信息,可以在客戶端被還原。
  • 客戶端解密信息
    客戶端用之前生成的私鑰解密服務(wù)端傳過來的信息,于是獲取了解密后的內(nèi)容。整個過程第三方即使監(jiān)聽到了數(shù)據(jù),也束手無策。

異常解決

問題描述

在4.x系統(tǒng)上通過HTTPS進行訪問產(chǎn)生如下異常:

  • javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x65bc0ad8: Failure in SSL library, usually a protocol error
    error:1407743E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert inappropriate fallback (external/openssl/ssl/s23_clnt.c:744 0x5cf4ed74:0x00000000)

原因

Android4.x系統(tǒng)對TLS的支持存在版本差異,具體細節(jié)請看以下分析

分析

首先我們查看一下Google關(guān)于SSLEngine的官方文檔說明

這里截取不同Android版本針對于TLS協(xié)議的默認配置圖如下:

這里寫圖片描述

從上圖可以得出如下結(jié)論:

  • TLSv1.0從API 1+就被默認打開
  • TLSv1.1和TLSv1.2只有在API 20+ 才會被默認打開
  • 也就是說低于API 20+的版本是默認關(guān)閉對TLSv1.1和TLSv1.2的支持,若要支持則必須自己打開

有了以上關(guān)于Android SSLEngine相關(guān)知識的鋪墊,讓我們來測試一下這次目標(biāo)案例的域名 fort.sports.baofeng.com

我們可以在QUALYS SSL LABS測試它對ssl支持的版本
這里截取SSL報告中對我們有用的一部分,如下圖

這里寫圖片描述
  • 剛開始服務(wù)器配置只支持TLS1.2,SSL報告的結(jié)果也驗證了這一點
  • 可以看出大部分2.x、4.x的Android系統(tǒng)都會報Server sent fatal alert:handshake_failure,而5.0、6.0、7.0的Android系統(tǒng)在Hanshake Simulation中表現(xiàn)正常,因為它們支持TLS1.2

這就能解釋為什么大部分4.xAndroid系統(tǒng)在進行HTTPS訪問時產(chǎn)生上述異常

解決方案

  • 我們首先想到的是,可以讓服務(wù)器配置兼容支持TLS1.0、TLS1.1、TLS1.2,這樣客戶端就不需要做任何處理,完美兼容
  • 經(jīng)過測試,這個是可以的(測試手機包括Lenovo K920 4.4.2,Lenovo K30-E 4.4.4)

我們再次查看SSL報告中的幾個關(guān)鍵結(jié)果:

這里寫圖片描述

從上圖可以看出,服務(wù)器配置已經(jīng)可以支持TLS1.0、TLS1.1、TLS1.2
從下圖可以看出,Handshake Simulation在Android 4.x系統(tǒng)也可以正常運作了

這里寫圖片描述

或許,你以為這樣就完美了,但是,你有沒有想到過這樣一種情況,當(dāng)你所訪問的域名服務(wù)器只支持TLS1.2,那Android4.x系統(tǒng)應(yīng)該如何應(yīng)對那

答案:想辦法讓Android4.x打開對TLS1.1、TLS1.2的支持

  • 假設(shè)你的網(wǎng)絡(luò)請求庫使用的是okhttp,在APP中可以這樣初始化OkHttpClient,這里通過在AppParams中配置isBypassAuthen,來判斷是否繞過認證,也就是無條件信任所有HTTPS網(wǎng)站
  • 這里只是 單向認證 客戶端對服務(wù)端證書的單向認證
private void initHttpsClient() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(30000L, TimeUnit.MILLISECONDS)
                .readTimeout(30000L, TimeUnit.MILLISECONDS)
                .addInterceptor(new LoggerInterceptor("OkHttpClient"))
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
        if(AppParams.isBypassAuthen){
            HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(null, null, null);
            builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
        }else{
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
                try {
                    sslContext.init(null, null, null);
                } catch (KeyManagementException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }

            SSLSocketFactory socketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
            builder.sslSocketFactory(socketFactory,new HttpsUtils.UnSafeTrustManager());
        }
        OkHttpClient okHttpClient = builder
                .build();
        OkHttpUtils.initClient(okHttpClient);
    }

具體怎么使用HTTPS,參考HttpsUtils:

public class HttpsUtils
{
    public static class SSLParams
    {
        public SSLSocketFactory sSLSocketFactory;
        public X509TrustManager trustManager;
    }

    public static SSLParams getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password)
    {
        SSLParams sslParams = new SSLParams();
        try
        {
            TrustManager[] trustManagers = prepareTrustManager(certificates);
            KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            X509TrustManager trustManager = null;
            if (trustManagers != null)
            {
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            } else
            {
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{trustManager},null);
            sslParams.sSLSocketFactory = sslContext.getSocketFactory();
            sslParams.trustManager = trustManager;
            return sslParams;
        } catch (NoSuchAlgorithmException e)
        {
            throw new AssertionError(e);
        } catch (KeyManagementException e)
        {
            throw new AssertionError(e);
        } catch (KeyStoreException e)
        {
            throw new AssertionError(e);
        }
    }

    private class UnSafeHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }

    public static class UnSafeTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException
        {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException
        {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new java.security.cert.X509Certificate[]{};
        }
    }

    private static TrustManager[] prepareTrustManager(InputStream... certificates)
    {
        if (certificates == null || certificates.length <= 0) return null;
        try
        {

            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates)
            {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try
                {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e)

                {
                }
            }
            TrustManagerFactory trustManagerFactory = null;

            trustManagerFactory = TrustManagerFactory.
                    getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            return trustManagers;
        } catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        } catch (CertificateException e)
        {
            e.printStackTrace();
        } catch (KeyStoreException e)
        {
            e.printStackTrace();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;

    }

    private static KeyManager[] prepareKeyManager(InputStream bksFile, String password)
    {
        try
        {
            if (bksFile == null || password == null) return null;

            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();

        } catch (KeyStoreException e)
        {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e)
        {
            e.printStackTrace();
        } catch (CertificateException e)
        {
            e.printStackTrace();
        } catch (IOException e)
        {
            e.printStackTrace();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers)
    {
        for (TrustManager trustManager : trustManagers)
        {
            if (trustManager instanceof X509TrustManager)
            {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }

    private static class MyTrustManager implements X509TrustManager
    {
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;

        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException
        {
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
        {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
        {
            try
            {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce)
            {
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[0];
        }
    }
}

自行實現(xiàn)SSLSocketFactory ,實現(xiàn)對TLSv1.1、TLSv1.2的支持

public class Tls12SocketFactory extends SSLSocketFactory {
    private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};

    final SSLSocketFactory delegate;

    public Tls12SocketFactory(SSLSocketFactory base) {
        this.delegate = base;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return patch(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return patch(delegate.createSocket(address, port, localAddress, localPort));
    }

    private Socket patch(Socket s) {
        if (s instanceof SSLSocket) {
            ((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
        }
        return s;
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 摘要:下文是關(guān)于HTTPS的介紹,在用戶訪問安全、內(nèi)容傳輸安全等使用場景中,阿里云CDN都可以提供相應(yīng)的HTTPS...
    肆虐的悲傷閱讀 534評論 0 0
  • 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文傳播,帶來了三大風(fēng)險。 竊聽風(fēng)險(eavesdr...
    高思陽閱讀 349評論 0 0
  • 本文大部分內(nèi)容摘自:http://www.wosign.com/faq/faq2016-0309-04.htm 尊...
    E狼閱讀 1,606評論 0 3
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學(xué)已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,788評論 16 22
  • 創(chuàng)業(yè)是很多人的夢想,多少人為了理想和不甘選擇了創(chuàng)業(yè)來實現(xiàn)自我價值,我就是其中一個。 創(chuàng)業(yè)后,我由女人變成了超人,什...
    亦寶寶閱讀 1,982評論 4 1

友情鏈接更多精彩內(nèi)容