Android使用https与服务器交互的正确姿势

Posted wenhui92

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android使用https与服务器交互的正确姿势相关的知识,希望对你有一定的参考价值。

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与服务器交互的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章

thrift生产环境服务端使用的正确姿势

Android开发 - 获取系统输入法高度的正确姿势

TCP/IP三次握手与四次挥手的正确姿势

极智开发 | mac安装jupyter notebook的正确姿势

Git Flow 的正确使用姿势

安卓7.0+https抓包新姿势(无需Root)