Android 开发经验集Android开发Android开发经验谈

关于 Android okhttp 使用 HTTPS 的学习记录

2017-11-08  本文已影响50人  4aecbb1c9413

起因

之前因为手机应用的安全性问题特意的组织讨论了一下,鉴于项目特性(功能验证),并非是实际场景应用,以及使用加密算法如果一旦数量级过高可能会造成服务器的负担,所以初步考虑先把 HTTPS 连接调通,保证基础的通道安全.

想法

因为服务端我并没有涉及,所以服务端的 HTTPS 相关配置由其他人去更改,在这个期间还是希望自己能和服务器端同步动作,但是没有一个支持 HTTPS 的后端测试起来相当麻烦,所以打算借助 GO 语言实现一个简易的支持 HTTPS 的服务端(真的很简易,就两行代码,但是生成 SSL 证书可是苦恼了我好久).

证书生成

关于各种证书这方面到现在我还没有具体的弄明白,可能是我太愚笨了,不过按照 Google 中的各种大神分享出来的相关资料还是弄出了一个可以用的证书.

以下是我生成证书的流程(此处只做记录,仅供参考,系统为 win7 64位):

服务器端搭建与证书验证

其中 "server.crt" 即使上文生成的服务器证书, "server.key" 即使上文生成的服务器密钥

Android 应用使用 HTTPS 连接

这里我们尝试用 okhttp 访问 HTTP 连接的方式来访问 HTTPS 连接,会报出以下错误:

java.security.cert.CertPathValidatorException:
    Trust anchor for certification path not found.

以上错误表明我们服务器的证书不可信,也就是因为我们的服务器证书是由自己签名生成的,所以没有被信任,如果你尝试用这种方式访问 https://www.baidu.com 你就会发现你能访问成功,因为百度的正式是由 CA 机构签名办法的,得到了信任,我们自签名的当然就没有这种待遇了.那么如何解决这个问题呢?

其实和电脑端的解决办法差不多,那就是由我们自己效验证书并信任.

//从 server.crt 中读取出来的字符串
String CER_CLIENT = "-----BEGIN CERTIFICATE-----\n" +
            "MIICIzCCAYwCCQC+PtNg8W5AwTANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJD\n" +
            "TjEQMA4GA1UECAwHQmVpSmluZzEQMA4GA1UEBwwHQmVpamluZzENMAsGA1UECgwE\n" +
            "TXlDQTESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE3MDIyMzAzMTY1MloXDTE3MDMy\n" +
            "NTAzMTY1MlowWDELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaUppbmcxEDAOBgNV\n" +
            "BAcMB0JlaUppbmcxETAPBgNVBAoMCE15U2VydmVyMRIwEAYDVQQDDAlsb2NhbGhv\n" +
            "c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMo9KveK/dTgcX7Yv+Q4LaFF\n" +
            "N3s22ahcYS/RgSH5gCQ11iVDylsBRYcwRY9ayTIbdOW/eZpuWZkiju0cBPprj3/1\n" +
            "fmWW1lMcI/vN96spXSJ7pbODDn5IJS5nU+bqRI5FEx2jzdQxLL1NZ+OkoN3GECpn\n" +
            "JKwdB734cX5xnJGM77nlAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAN+Aa4oFDVvSs\n" +
            "Vuts6lxnZegeY1+UQlYNqJNfUh4RvHt7dBVvbqdwJKTxi7FrYjVfc/83FqC3RzYG\n" +
            "4CwesgdHon8a/nd6+zT2NVi4QUfKG5XopvTobpSd8sZq2I7uVM3q3UPIBz3yVaNz\n" +
            "YTMaf4xo5Ys3/1pm0/kO5oPDWp5A3Pw=\n" +
            "-----END CERTIFICATE-----";
/**
* 实现了 X509TrustManager
* 通过此类中的 checkServerTrusted 方法来确认服务器证书是否正确
*/
class MyTrustManager implements X509TrustManager {
       X509Certificate cert;

       MyTrustManager(X509Certificate cert) {
           this.cert = cert;
       }

       @Override
       public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
           // 我们在客户端只做服务器端证书校验。
       }

       @Override
       public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
           // 确认服务器端证书和代码中 hard code 的 CRT 证书相同。
           if (chain[0].equals(this.cert)) {
               Log.i("Jin", "checkServerTrusted Certificate from server is valid!");
               return;// found match
           }
           throw new CertificateException("checkServerTrusted No trusted server cert found!");
       }

       @Override
       public X509Certificate[] getAcceptedIssuers() {
           return new X509Certificate[0];
       }
   }
   /**
  * 进行 HTTPS 访问测试
  * @throws NoSuchAlgorithmException
  * @throws KeyManagementException
  */
 private void testHttps() throws NoSuchAlgorithmException, KeyManagementException {
     SSLContext sc = SSLContext.getInstance("TLS");
     //信任证书管理,这个是由我们自己生成的,信任我们自己的服务器证书
     TrustManager tm = new MyTrustManager(readCert(CER_CLIENT));
     sc.init(null, new TrustManager[]{
             tm
     }, null);
     OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
             .sslSocketFactory(sc.getSocketFactory(), (X509TrustManager) tm)
             .hostnameVerifier(hostnameVerifier)
             .build();
     Call call = okHttpClient.newCall(new Request.Builder().url("https://192.168.0.232:8081").get().build());
     call.enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {
             Log.i("Jin", "Failure :" + e.getMessage());
         }

         @Override
         public void onResponse(Call call, Response response) throws IOException {
             final String res = response.body().string();
             Log.i("Jin", "Response :" + res);
         }
     });
 }

 //主机地址验证
 final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
     @Override
     public boolean verify(String hostname, SSLSession session) {
         return hostname.equals("192.168.0.232");
     }
 };

 /**
  * 根据字符串读取出证书
  * @param cer
  * @return
  */
 private static X509Certificate readCert(String cer) {
     if (cer == null || cer.trim().isEmpty())
         return null;
     InputStream caInput = new ByteArrayInputStream(cer.getBytes());
     X509Certificate cert = null;
     try {
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         cert = (X509Certificate) cf.generateCertificate(caInput);
     } catch (Exception e) {
         e.printStackTrace();
     } finally {
         try {
             if (caInput != null) {
                 caInput.close();
             }
         } catch (Throwable ex) {
         }
     }
     return cert;
 }

运行后可以看到控制台返回:

I/Jin: Response :hello, world!

通过以上的方法就可以访问我们自己签名的 HTTPS 服务器了.

Android Https 相关完全解析 当 OkHttp 遇到 Https

HTTPS 证书生成原理和部署细节

上一篇 下一篇

猜你喜欢

热点阅读