Android Https心得

Https通信過程

366784-20160127222221785-258650029.png

1 客戶端發(fā)起一個https的請求,把自身支持的一系列Cipher Suite(密鑰算法套件,簡稱Cipher)發(fā)送給服務(wù)端

2 服務(wù)端,接收到客戶端所有的Cipher后與自身支持的對比,如果不支持則連接斷開,反之則會從中選出一種加密算法和HASH算法

以證書的形式返回給客戶端 證書中還包含了 公鑰 頒證機(jī)構(gòu) 網(wǎng)址 失效日期等等。

3 客戶端收到服務(wù)端響應(yīng)后會做以下幾件事

  • 驗(yàn)證證書的合法性
    頒發(fā)證書的機(jī)構(gòu)是否合法與是否過期,證書中包含的網(wǎng)站地址是否與正在訪問的地址一致等證書驗(yàn)證通過后,在瀏覽器的地址欄會加上一把小鎖(每家瀏覽器驗(yàn)證通過后的提示不一樣 不做討論)

  • 生成隨機(jī)密碼
    如果證書驗(yàn)證通過,或者用戶接受了不授信的證書,此時瀏覽器會生成一串隨機(jī)數(shù),然后用證書中的公鑰加密。

  • HASH握手信息
    用最開始約定好的HASH方式,把握手消息取HASH值, 然后用 隨機(jī)數(shù)加密 “握手消息+握手消息HASH值(簽名)” 并一起發(fā)送給服務(wù)端

     在這里之所以要取握手消息的HASH值,主要是把握手消息做一個簽名,用于驗(yàn)證握手消息在傳輸過程中沒有被篡改過。
    
  • 服務(wù)端拿到客戶端傳來的密文,用自己的私鑰來解密握手消息取出隨機(jī)數(shù)密碼,再用隨機(jī)數(shù)密碼 解密 握手消息與HASH值,并與傳過來的HASH值做對比確認(rèn)是否一致。然后用隨機(jī)密碼加密一段握手消息(握手消息+握手消息的HASH值 )給客戶端。

  • 客戶端用隨機(jī)數(shù)解密并計算握手消息的HASH,如果與服務(wù)端發(fā)來的HASH一致,此時握手過程結(jié)束,之后所有的通信數(shù)據(jù)將由之前瀏覽器生成的隨機(jī)密碼并利用對稱加密算法進(jìn)行加密因?yàn)檫@串密鑰只有客戶端和服務(wù)端知道,所以即使中間請求被攔截也是沒法解密數(shù)據(jù)的,以此保證了通信的安全

非對稱加密算法:RSA,DSA/DSS 在客戶端與服務(wù)端相互驗(yàn)證的過程中用的是對稱加密

對稱加密算法:AES,RC4,3DES 客戶端與服務(wù)端相互驗(yàn)證通過后,以隨機(jī)數(shù)作為密鑰時,就是對稱加密

HASH算法:MD5,SHA1,SHA256 在確認(rèn)握手消息沒有被篡改時

Android OKHttp Https配置

X509TrustManager:

在JSSE中,證書信任管理器類就是實(shí)現(xiàn)了接口X509TrustManager的類。

接口X509TrustManager有下述三個公有的方法需要我們實(shí)現(xiàn):

⑴ void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
該方法檢查客戶端的證書,若不信任該證書則拋出異常。由于我們不需要對客戶端進(jìn)行認(rèn)證,因此我們只需要執(zhí)行默認(rèn)的信任管理器的這個方法。JSSE中,默認(rèn)的信任管理器類為TrustManager。

⑵ void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
該方法檢查服務(wù)器的證書,若不信任該證書同樣拋出異常。通過自己實(shí)現(xiàn)該方法,可以使之信任我們指定的任何證書。在實(shí)現(xiàn)該方法時,也可以簡單的不做任何處理,即一個空的函數(shù)體,由于不會拋出異常,它就會信任任何證書。

⑶ X509Certificate[] getAcceptedIssuers()
返回受信任的X509證書數(shù)組。
  • 若server使用的證書是由認(rèn)證的CA機(jī)構(gòu)頒發(fā)的,則如此配置即可(OkHttp等均已支持):
    private X509TrustManager systemDefaultTrustManager() {
    try {
      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
          TrustManagerFactory.getDefaultAlgorithm());
      trustManagerFactory.init((KeyStore) null);
      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
      if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
            + Arrays.toString(trustManagers));
      }
      return (X509TrustManager) trustManagers[0];
    } catch (GeneralSecurityException e) {
      throw new AssertionError(); // The system has no TLS. Just give up.
    }
  }

  private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
    try {
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, new TrustManager[] { trustManager }, null);
      return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
      throw new AssertionError(); // The system has no TLS. Just give up.
    }
  }

  • 若使用自簽名證書,則客戶端需要將server生成的xx.cer放置到assets目錄下:
    /**
     * 設(shè)置https certificate
     *
     * @param context
     * @param certificateName must be in assets directory such as "abf.cer"
     */
    public void setSSLSocketFactory(Context context, String certificateName) {
        try {
            CertificateFactory tCertFactory = CertificateFactory.getInstance("X.509");
            X509Certificate tJDBCert = readJDBCertFromAsset(context, certificateName, tCertFactory);
            X509TrustManager tTrustMgr = newTrustManager(tJDBCert);
            SSLContext tSSLContext = newSSLContext(tTrustMgr);
            mOkHttpClient.newBuilder().sslSocketFactory(new NoSSLv3SocketFactory(tSSLContext.getSocketFactory()), tTrustMgr);
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
    }
    /**
     * xxx.cer證書 -> X509Certificate
     */
    private X509Certificate readJDBCertFromAsset(Context context, String certificateName, CertificateFactory pCertFactory) throws IOException, CertificateException {
        InputStream caInput = context.getAssets().open(certificateName);
        if (caInput == null) {
            return null;
        }
        try {
            X509Certificate tCert = (X509Certificate)pCertFactory.generateCertificate(caInput);
            tCert.checkValidity();
            return tCert;
        } finally {
            caInput.close();
        }
    }
    /**
     * X509TrustManager實(shí)例化 Android 自帶的一套https認(rèn)證機(jī)制
     */
    private X509TrustManager newTrustManager(final X509Certificate privateCert) {
        // Accepted CA is only the one installed in the store.
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                if (null == chain || 0 == chain.length) {
                    throw new CertificateException("Certificate chain is invalid.");
                }

                if (null == authType || 0 == authType.length()) {
                    throw new CertificateException("Authentication type is invalid.");
                }

                for (X509Certificate cert : chain) {

                    // Make sure that it hasn't expired.
                    cert.checkValidity();

                    // Verify the certificate's public key chain.
                    try {
                        cert.verify(privateCert.getPublicKey());
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    } catch (InvalidKeyException e) {
                        e.printStackTrace();
                    } catch (NoSuchProviderException e) {
                        e.printStackTrace();
                    } catch (SignatureException e) {
                        e.printStackTrace();
                    }
                }
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[]{};
            }
        };
    }
    /**
     * X509TrustManager -> SSLContext
     */
    private SSLContext newSSLContext(X509TrustManager pTrustManager) throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext tSSLContext = SSLContext.getInstance("TLS");
        tSSLContext.init(null, new TrustManager[]{pTrustManager}, null);
        return tSSLContext;
    }

解釋一下代理如何抓取Https請求:

一般我們可以抓取的https請求,server端的證書都是由認(rèn)證CA頒發(fā)的,所以
X509TrustManager可以使用默認(rèn)的那套,從系統(tǒng)根證書信任列表中去挨個認(rèn)證。
所以那Charles為例,如何抓取https信息呢? 首先電腦上(由于電腦是代理,相當(dāng)于服務(wù)器)
信任Charles證書,然后在手機(jī)上安裝Charles證書,兩端證書的指紋是一樣的。當(dāng)手機(jī)發(fā)出
https請求時,根據(jù)https通信過程,電腦會把charles的證書下發(fā)給手機(jī),而手機(jī)上由于之前
安裝了Charles證書顯然可以驗(yàn)證通過,所以手機(jī)端會生成對稱秘鑰,從此以它進(jìn)行加解密。但
是,對于自簽名的證書,由于使用的X509TrustManager是自定義的,也就是只能驗(yàn)證自簽名
證書,不會向手機(jī)的根信任列表去挨個校驗(yàn),所以這種方式無法抓取https。

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

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

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