1. 程式人生 > >Android使用https與服務器交互的正確姿勢

Android使用https與服務器交互的正確姿勢

新的 println tps slc res tex 處理 熱點 如果

HTTPS 使用 SSL 在客戶端和服務器之間進行加密通信,錯誤地使用 SSL ,將會導致其它人能夠攔截網絡上的應用數據。

使用一個包含公鑰及與其匹配的私鑰的證書配置服務器,作為 SSL 客戶端與服務器握手的一部分,服務器將通過使用公鑰加密簽署其證書來證明自己具有私鑰。

主機平臺一般包含其信任的知名 CA 的列表。從 Android 4.2 開始,Android 包含在每個版本中更新的 100 多個 CA。CA 具有一個證書和一個私鑰,為服務器發放證書時,CA 使用其私鑰簽署服務器證書。然後,客戶端可以驗證該服務器是否具有平臺已知的 CA 發放的證書。

如果擁有一個知名 CA 發放證書的服務器,那麽可以用以下代碼直接發起 HTTPS 請求

URL url = new URL("https://www.cnblogs.com");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

對!就是這麽簡單。 Android 會對驗證證書和主機名做處理,你不用考慮這些細節。

如果驗證服務器證書出現 SSLHandshakeException 異常,那麽原因可能是頒發服務器證書的 CA 是 Android 系統未知的,或者是自簽署的服務器證書。

為了解決證書驗證失敗的問題,我們可以使用自定義的 TrustManager 使 HttpsURLConnection 信任特定的 CA 。

// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.close();
}

// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

InputStream 獲取一個特定的 CA,用該 CA 創建 KeyStore,然後用後者創建和初始化 TrustManagerTrustManager 是系統用於從服務器驗證證書的工具,可以使用一個或多個 CA 從 KeyStore 創建,而創建的 TrustManager 將僅信任這些 CA。

很多網站和博客介紹一種非常糟糕的解決方案來通過驗證

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new 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 X509Certificate[0];
    }
}}, null);
URLConnection urlConnection = url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

使用一個沒有任何作用的 TrustManager。這樣做等同於沒加密通信,因為任何人都可以在公共 WLAN 熱點下,使用偽裝成服務器的代理發送數據,通過 DNS 欺騙攻擊用戶。然後,攻擊者可以記錄密碼和其他個人數據。此方法之所以有效是因為攻擊者可以生成一個證書,且沒有可以真正驗證證書是否來自值得信任的來源的 TrustManager,從而使你的應用可與任何人通信。

Android使用https與服務器交互的正確姿勢