Android Https通信 - Go语言中文社区

Android Https通信


Https概述

为何需要HTTPS

HTTP协议是没有加密的明文传输协议,如果APP采用HTTP传输数据,则会泄露传输内容,可能被中间人劫持,修改传输的内容。如下图所示就是典型的APP HTTP通信被运营商劫持修改,插入广告:
这里写图片描述
上图是在我的住处,用WiFi打开某APP,页面底部出现了一个拆红包的广告,点开以后是一个安装APP的页面,如果我用联通的4G网络打开,就不会出现这种情况,说明小区运营商劫持了HTTP通信,往APP的通信中加入了自己的推广内容,还有一些低俗的推广广告,这很影响用户体验。一些别有用心的人通过搭建公共WiFi,进行流量劫持、嗅探,可以获得通过HTTP传输的敏感信息。

为了保护用户的信息安全、保护自己的商业利益,减少攻击面,我们需要保障通信信道的安全,采用开发方便的HTTPS是比较好的方式,比用私有协议要好,省时省力。但是如果HTTPS使用不当,就很难起到应有的保护效果。

HTTPS原理

HTTPS(Hyper Text Transfer Protocol Secure),是一种基于SSL/TLS的HTTP,所有的HTTPS数据都是在SSL/TLS协议封装之上进行传输的,端口为443。HTTPS协议是在HTTP协议的基础上,添加了SSL/TLS握手以及数据加密传输,也属于应用层协议。所以,研究HTTPS协议原理,最终就是研究SSL/TLS协议。

SSL/TLS协议作用

不使用SSL/TLS的HTTP通信,就是不加密的通信,所有的信息明文传播,带来了三大风险:

  • 窃听风险:第三方可以获知通信内容。
  • 篡改风险:第三方可以修改通知内容。
  • 冒充风险:第三方可以冒充他人身份参与通信。

SSL/TLS协议是为了解决这三大风险而设计的,希望达到:

  • 所有信息都是加密传输,第三方无法窃听。
  • 具有校验机制,一旦被篡改,通信双方都会立刻发现。
  • 配备身份证书,防止身份被冒充。

基本的运行过程

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。但是这里需要了解两个问题的解决方案。
(1)如何保证公钥不被篡改?
解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
(2)公钥加密计算量太大,如何减少耗用的时间?
解决方法:每一次对话(session),客户端和服务器端都生成一个“对话密钥”(session key),用它来加密信息。由于“对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密“对话密钥”本身,这样就减少了加密运算的消耗时间。

因此,SSL/TLS协议的基本过程是这样的:

  • 客户端向服务器端索要并验证公钥。
  • 双方协商生成“对话密钥”。
  • 双方采用“对话密钥”进行加密通信。

上面过程的前两步,又称为“握手阶段”。

握手阶段的详细过程

“握手阶段”涉及四次通信,需要注意的是,“握手阶段”的所有通信都是明文的。
(1)客户端发出请求(ClientHello)
首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。在这一步中,客户端主要向服务器提供以下信息:

  • 支持的协议版本,比如TLS 1.0版。
  • 一个客户端生成的随机数,稍后用于生成“对话密钥”。
  • 支持的加密方法,比如RSA公钥加密。
  • 支持的压缩方法。

这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应用向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。

(2)服务器回应(ServerHello)
服务器收到客户端请求后,向客户端发出回应,这叫做ServerHello。服务器的回应包含以下内容:

  • 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
  • 一个服务器生成的随机数,稍后用于生成“对话密钥”。
  • 确认使用的加密方法,比如RSA公钥加密。
  • 服务器证书。

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供“客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。

(3)客户端回应(ClientCertificate)
客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁发,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项消息。

  • 一个随机数。该随机数用服务器公钥加密,防止被窃听。
  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项通常也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项随机数,是整个握手阶段出现的第三个随机数,又称“pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把“会话密钥”。

(4)服务器的最后回应(ServerCertificate)
服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的“会话密钥”。然后,向客户端最后发送下面信息。

  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发生的所有内容的hash值,用来供客户端校验。

这里写图片描述
至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用“会话密钥”加密内容。

客户端访问CA认证的https网站

根据上面的流程,我们可以看到服务器端会有一个证书,在交互过程中客户端需要去验证证书的合法性,对于CA机构颁发的证书当然我们会直接认为合法。对于自己造的证书,那么我们就需要去校验合法性了,也就是说我们需要让客户端去信任这个证书才可以畅通的进行通信。

这里我们举一个CA认证网站的例子:https://www.baidu.com

    private void getDatas() {
        AsyncHttpClient client = new AsyncHttpClient();
        client.get("https://www.baidu.com", new TextHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, String responseBody) {
                showlog("onSuccess reponse=" + responseBody);
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
                showlog("onFailure error="+error.toString());
            }
        });
    }

运行,可以看到以上程序会正确走到onSuccess并获取responseBody的值。

我们再举一个自签名(非CA认证)网站的例子,就是12306网站了,https://kyfw.12306.cn/otn/,我们修改一下请求url,运行:

onFailure error=javax.net.ssl.SSLHandshakeException: 
java.security.cert.CertPathValidatorException:Trust anchor for certification path not found.

果然,对于自签名网站,由于证书没有被校验而不被信任,会报error得不到正确的结果。
对于自己造的证书,我们需要去校验合法性,也就是说我们需要让客户端去信任这个证书才可以畅通的进行通信。那么如何访问自签名网站呢?看下面。

客户端访问自签名https网站

本节同样以12306为例子来说明如何去访问自签名证书的网站。一般有两种方式实现,一是信任所有的证书,也就是跳过证书合法性校验这一步骤,对于这种做法肯定是有风险的;二是校验证书,证书合法才能访问。

使用HttpClient

我们首先写一个例子,使用HttpClient来访问CA认证的https网站。

public class MainActivity extends AppCompatActivity {
    private TextView text;
    private CreateHttpsClientConnTask httpsClientConnTask;
    String https_url = "https://www.baidu.com";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        runHttpsClient();
    }

    private void runHttpsClient() {
        if (httpsClientConnTask == null || httpsClientConnTask.getStatus() == AsyncTask.Status.FINISHED) {
            httpsClientConnTask = new CreateHttpsClientConnTask();
            httpsClientConnTask.execute();
        }
    }

    private class CreateHttpsClientConnTask extends AsyncTask<Void, Void, Void> {
        private StringBuffer sBuffer = new StringBuffer();

        @Override
        protected Void doInBackground(Void... params) {
            HttpUriRequest request = new HttpGet(https_url);
            HttpClient httpClient = HttpUtils.getHttpsClient(); //访问普通的CA认证的https网站
            try {
                HttpResponse httpResponse = httpClient.execute(request);
                if (httpResponse != null) {
                    StatusLine statusLine = httpResponse.getStatusLine();
                    if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK) {
                        BufferedReader reader = null;
                        try {
                            reader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent(), "UTF-8"));
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                sBuffer.append(line);
                            }
                        } catch (Exception e) {
                            Log.e("https", e.getMessage());
                        } finally {
                            if (reader != null) {
                                reader.close();
                                reader = null;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                Log.e("https", e.getMessage());
            } finally {
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (!TextUtils.isEmpty(sBuffer.toString())) {
                text.setText(sBuffer.toString());
            }
        }
    }

    private void showlog(String info) {
        System.out.print("Watson " + info + "n");
    }
}

工具类:

public class HttpUtils {
    //普通的CA认证的https网站
    public static HttpClient getHttpsClient() {
        BasicHttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);
        SchemeRegistry schReg = new SchemeRegistry();
        //设置http和https支持
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
        return new DefaultHttpClient(connMgr, params);
    }

    //信任所有证书
    public static HttpClient getMyHttpsClient() {
        BasicHttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);
        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", MySSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
        return new DefaultHttpClient(connMgr, params);
    }

    //校验证书
    public static HttpClient getCustomerKeyStoreClient(Context context) {
        BasicHttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);
        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", CustomerSocketFactory.getSocketFactory(context), 443));
        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
        return new DefaultHttpClient(connMgr, params);
    }
}

对于工具类我们暂且看方法getHttpsClient(),Android使用DefaultHttpClient建立HTTPS连接,关键需要加入对HTTPS的支持:

schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); 

加入对HTTPS的支持,就可以有效的建立HTTPS连接了,例如“https://www.baidu.com”,效果如下:
这里写图片描述

但是现在访问自签名https网站却还不行,例如“https://kyfw.12306.cn/otn/”,因为它使用了不被系统承认的自定义证书,会报出如下问题:No peer certificate。

信任所有证书:

解决证书不被系统承认的方法,就是跳过系统校验。要跳过系统校验,就不能再使用系统标准的SSLSocketFactory了,需要自定义一个。然后为了在这个自定义的SSLSocketFactory里跳过校验,还需要自定义一个X509TrustManager(Android采用的是X509验证),建立我们的验证规则,在其中忽略所有校验,即TrustAll。

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);
        // Android 采用X509的证书信息机制
        TrustManager tm = new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            // Create a trust manager that does not validate certificate chains
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
        };
        // Install the all-trusting trust manager
        sslContext.init(null, new TrustManager[]{tm}, null);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    public static SSLSocketFactory getSocketFactory() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory factory = new MySSLSocketFactory(trustStore);
            //factory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  //允许所有主机的验证
            return factory;
        } catch (Exception e) {
            e.getMessage();
            return null;
        }
    }
}

同时,需要修改DefaultHttpClient的register方法,改为自己构建的sslsocket:

    public static HttpClient getMyHttpsClient() {
        BasicHttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);
        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", MySSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
        return new DefaultHttpClient(connMgr, params);
    }

这样就可以成功的访问自签名https网站了。
我们把请求url改成 https://kyfw.12306.cn/otn/,运行结果:
这里写图片描述

缺陷:
不过,虽然这个方案使用了HTTPS,客户端和服务器端的通信内容得到了加密,嗅探程序无法得到传输的内容,但是无法抵挡“中间人攻击”。例如,在内网配置一个DNS,把目标服务器域名解析到本地的一个地址,然后在这个地址上使用一个中间服务器作为代理,它使用一个假的证书与客户端通讯,然后再由这个代理服务器作为客户端连接到实际的服务器,用真的证书与服务器通讯。这样所有的通讯内容都会经过这个代理,而客户端不会感知,这是由于客户端不校验服务器公钥证书导致的。

校验证书:

为了防止上面方案可能导致的“中间人攻击”,我们可以下载服务器端公钥证书,然后将公钥证书编译到Android应用中,由应用自己来验证证书。

导出12306证书:
12306页面—>右击属性
这里写图片描述
证书–>详细信息—>复制到文件
这里写图片描述
选择存储格式(这里我选的是DER编码二进制 X.509)—>选择存储路径、文件名(srca.cer)
至此,证书文件就成功导出了,我们将srca.cer文件放到项目的Assert目录下,以便后面使用。
这里写图片描述

方法一:
使用自定义KeyStore实现连接,思路和TrushAll差不多,也是需要一个自定义的SSLSokcetFactory,不过因为还需要验证证书,因此不需要自定义TrustManager了。

public class CustomerSocketFactory extends SSLSocketFactory {

    public CustomerSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);
    }

    public static SSLSocketFactory getSocketFactory(Context context) {
        InputStream input = null;
        try {
            input = context.getAssets().open("srca.cer");// 下载的证书放到项目中的assets目录中
            CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
            Certificate cer = cerFactory.generateCertificate(input);
            KeyStore trustStore = KeyStore.getInstance("PKCS12", "BC");
            trustStore.load(null, null);
            trustStore.setCertificateEntry("trust", cer);
            SSLSocketFactory factory = new CustomerSocketFactory(trustStore);
            return factory;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                input = null;
            }
        }
    }
}

同时,需要修改DefaultHttpClient的register方法,改为自己构建的sslsocket:

public static HttpClient getCustomerKeyStoreClient(Context context) {
        BasicHttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);
        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", CustomerSocketFactory.getSocketFactory(context), 443));
        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
        return new DefaultHttpClient(connMgr, params);
    }

方法二:
其实校验证书还有第二种方法:
有些人可能觉得把证书copy到assets目录下不舒服,其实我们还可以将证书中的内容提取出来,写成字符串常量,这样就不需要证书跟着app去打包了。
那么如何提取srca.cer证书的内容呢?命令:
keytool -printcert -rfc -file srca.cer

这里写图片描述

这样就可以提取到证书的内容了。有关keytool命令的使用可以参考这篇文章:http://blog.chinaunix.net/uid-17102734-id-2830223.html

我们重新改写CustomerSocketFactory类:

public class CustomerSocketFactory extends SSLSocketFactory {

    private static String CER_12306 = "-----BEGIN CERTIFICATE-----n" +
            "MIICsTCCAhqgAwIBAgIIODtw6bZEH1kwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ04xKTAnn" +
            "BgNVBAoTIFNpbm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMB4Xn" +
            "DTE0MDUyNjAxNDQzNloXDTE5MDUyNTAxNDQzNlowazELMAkGA1UEBhMCQ04xKTAnBgNVBAoTIFNpn" +
            "bm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRkwFwYDVQQLHhCUwY3vW6JiN2cNUqFOLV/Dn" +
            "MRYwFAYDVQQDEw1reWZ3LjEyMzA2LmNuMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8Cxlzn" +
            "+V/4KkUk8YTxVxzii7xp2gZPWuuVBiwQ6iwL98it75WNGiYCUasDXy3O8wY+PtZFvgEKkpHqQ1U6n" +
            "uemiHStthUS1xTBsU/TuXF6AHc+oduP6zCGKcUnHRAksRb8BGSgzBA/X3B9CUKnYa9YA2EBIYccrn" +
            "zIh6aRAjDHbvYQIDAQABo4GBMH8wHwYDVR0jBBgwFoAUeV62d7fiUoND7cdRiExjhSwAQ1gwEQYJn" +
            "YIZIAYb4QgEBBAQDAgbAMAsGA1UdDwQEAwIC/DAdBgNVHQ4EFgQUj/0m74jhq993ItPCldNHYLJ8n" +
            "84MwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4GBAEXeoTkvn" +
            "UVSeQzAxFIvqfC5jvBuApczonn+Zici+50Jcu17JjqZ0zEjn4HsNHm56n8iEbmOcf13fBil0aj4An" +
            "Qz9hGbjmvQSufaB6//LM1jVe/OSVAKB4C9NUdY5PNs7HDzdLfkQjjDehCADa1DH+TP3879N5zFoWn" +
            "DgejQ5iFsAh0n" +
            "-----END CERTIFICATE-----";

    public CustomerSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);
    }

    public static SSLSocketFactory getSocketFactory(Context context) {
        InputStream input = null;
        try {
//          input = context.getAssets().open("srca.cer");//下载的证书放到项目中的assets目录中
            input = new ByteArrayInputStream(CER_12306.getBytes("UTF-8")); //证书内容存放在String中
            CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
            Certificate cer = cerFactory.generateCertificate(input);
            KeyStore trustStore = KeyStore.getInstance("PKCS12", "BC");
            trustStore.load(null, null);
            trustStore.setCertificateEntry("trust", cer);
            SSLSocketFactory factory = new CustomerSocketFactory(trustStore);
            return factory;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                input = null;
            }
        }
    }
}

使用HttpsURLConnection

信任所有证书:

    private void runHttpsURLConnection() {
        if (httpsUrlConnTask == null || httpsUrlConnTask.getStatus() == AsyncTask.Status.FINISHED) {
            httpsUrlConnTask = new CreateHttpsUrlConnTask();
            httpsUrlConnTask.execute();
        }
    }

    private class CreateHttpsUrlConnTask extends AsyncTask<Void, Void, Void> {
        private StringBuffer sBuffer = new StringBuffer();

        @Override
        protected Void doInBackground(Void... params) {
            try {
                TrustManager tm = new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

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

                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{tm}, null);

                URL url = new URL(https_url);
                HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
                httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
             //   httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  //允许所有主机的验证
        httpsURLConnection.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
                final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        //未校验服务器证书的域名是否符合
                        return true;
                    }
                };
                httpsURLConnection.setHostnameVerifier(hostnameVerifier);

                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        sBuffer.append(line);
                    }
                } catch (Exception e) {
                    Log.e("https", e.getMessage());
                } finally {
                    if (reader != null) {
                        reader.close();
                        reader = null;
                    }
                }
            } catch (Exception e) {
                Log.e("https", e.getMessage());
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (!TextUtils.isEmpty(sBuffer.toString())) {
                text.setText(sBuffer.toString());
            }
        }
    }

校验证书:

方法一:
真正实现TrustManger的checkServerTrusted(),对服务器证书域名进行强校验或者真正实现HostnameVerifier的verify()方法。

    private class CreateHttpsUrlConnTask extends AsyncTask<Void, Void, Void> {
        private StringBuffer sBuffer = new StringBuffer();

        @Override
        protected Void doInBackground(Void... params) {
            try {
                InputStream input = getAssets().open("srca.cer");
                CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
                //其中serverCert是APP中预埋的服务器端公钥证书
                final X509Certificate serverCert = (X509Certificate) cerFactory.generateCertificate(input);

                TrustManager tm = new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
                        if (x509Certificates == null) {
                            throw new IllegalArgumentException("check server x509Certificates is null");
                        }
                        if (x509Certificates.length < 0) {
                            throw new IllegalArgumentException("check server x509Certificates is empty");
                        }
                        for (X509Certificate cert: x509Certificates) {
                            //检查服务器端证书签名是否有问题
                            cert.checkValidity();
                            try {
                                //和APP预埋证书作对比
                                cert.verify(serverCert.getPublicKey());
                            } catch (Exception e) {
                                showlog(e.toString());
                            }
                        }
                    }

                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{tm}, null);

                URL url = new URL(https_url);
                HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
                httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
             // httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  //允许所有主机的验证
                httpsURLConnection.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
                final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        //校验服务器证书的域名是否符合
//                        HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
//                        Boolean result = hv.verify("*/otn/", sslSession);
//                        return result;
                        return true;
                    }
                };
                httpsURLConnection.setHostnameVerifier(hostnameVerifier);

                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        sBuffer.append(line);
                    }
                } catch (Exception e) {
                    Log.e("https", e.getMessage());
                } finally {
                    if (reader != null) {
                        reader.close();
                        reader = null;
                    }
                }
            } catch (Exception e) {
                Log.e("https", e.getMessage());
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (!TextUtils.isEmpty(sBuffer.toString())) {
                text.setText(sBuffer.toString());
            }
        }
    }

方法二:
上面只是一种写法,还有另外一种写法证书锁定,直接用预埋的证书来生成TrustManger,过程如下:

    private void runHttpsURLConnection2() {
        if (httpsUrlConnTask2 == null || httpsUrlConnTask2.getStatus() == AsyncTask.Status.FINISHED) {
            httpsUrlConnTask2 = new CreateHttpsUrlConnTask2();
            httpsUrlConnTask2.execute();
        }
    }

    private class CreateHttpsUrlConnTask2 extends AsyncTask<Void, Void, Void> {
        private StringBuffer sBuffer = new StringBuffer();

        @Override
        protected Void doInBackground(Void... params) {
            try {
                //以X.509格式获取证书
                InputStream input = getAssets().open("srca.cer");
                CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
                Certificate cert = cerFactory.generateCertificate(input);

                //生成一个包含服务器证书的keystore
                String keyStoreType = KeyStore.getDefaultType();
                KeyStore keyStore = KeyStore.getInstance(keyStoreType);
                keyStore.load(null, null);
                keyStore.setCertificateEntry("cert", cert);

                //用包含服务器端证书的KeyStore生成一个TrustManager
                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
                trustManagerFactory.init(keyStore);

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

                URL url = new URL(https_url);
                HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
                httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                // httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  //允许所有主机的验证
                httpsURLConnection.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
                final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        //校验服务器证书的域名是否符合
//                        HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
//                        Boolean result = hv.verify("*/otn/", sslSession);
//                        return result;
                        return true;
                    }
                };
                httpsURLConnection.setHostnameVerifier(hostnameVerifier);

                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        sBuffer.append(line);
                    }
                } catch (Exception e) {
                    Log.e("https", e.getMessage());
                } finally {
                    if (reader != null) {
                        reader.close();
                        reader = null;
                    }
                }
            } catch (Exception e) {
                Log.e("https", e.getMessage());
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (!TextUtils.isEmpty(sBuffer.toString())) {
                text.setText(sBuffer.toString());
            }
        }
    }

使用OKHttp3.0

除了使用Android系统提供的HttpClient和HttpsURLconnection进行https通信,还有其他的第三方库可以使用,以OKhttp3.0为例:

    /** OkHttp请求Https网站 */
    private void runHttpsOkHttp() {
        try {
            //以X.509格式获取证书
            InputStream input = getAssets().open("srca.cer");
            CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
            Certificate cert = cerFactory.generateCertificate(input);

            //生成一个包含服务器证书的keystore
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("cert", cert);

            //用包含服务器端证书的KeyStore生成一个TrustManager
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
            trustManagerFactory.init(keyStore);

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);


            final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    //不校验服务器端证书域名
                    return true;
                }
            };
            OkHttpClient mOkHttpClient = new OkHttpClient().newBuilder().hostnameVerifier(hostnameVerifier)
                                                 .sslSocketFactory(sslContext.getSocketFactory()).build();
            Request request = new Request.Builder()
                    .url(https_url)
                    .build();
            Call call = mOkHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, final IOException e) {
                    showlog(e.getMessage());
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    final String str = response.body().string();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            text.setText(str);
                        }
                    });
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

WebView加载https页面

目前很多应用都用webview加载H5页面,如果服务端采用的是可信CA颁发的证书,在 webView.setWebViewClient(webviewClient) 时重载 WebViewClient的onReceivedSslError() ,如果出现证书错误,直接调用handler.proceed()会忽略错误继续加载证书有问题的页面,如果调用handler.cancel()可以终止加载证书有问题的页面,证书出现问题了,可以提示用户风险,让用户选择加载与否,如果是需要安全级别比较高,可以直接终止页面加载,提示用户网络环境有风险:

    ...
    web.setWebViewClient(webViewClient);
    ...

    /** WebView加载Https网站 */
    private void loadHttpsUrl() {
        web.loadUrl(https_url);
    }

    private WebViewClient webViewClient = new WebViewClient() {
        @Override
        public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
            final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            showlog("error toString()="+error.toString());
            showlog("error getPrimaryError()="+error.getPrimaryError());
            SslCertificate sslCertificate = error.getCertificate();
            showlog("sslCertificate toString()="+sslCertificate.toString());

            switch(error.getPrimaryError()) {
                case SslError.SSL_DATE_INVALID:
                    showlog(SslError.SSL_DATE_INVALID+" ssl date invalid");
                    break;
                case SslError.SSL_EXPIRED:
                    showlog(SslError.SSL_EXPIRED+" ssl expired");
                    break;
                case SslError.SSL_IDMISMATCH:
                    showlog(SslError.SSL_IDMISMATCH+" ssl id mismatch");
                    break;
                case SslError.SSL_INVALID:
                    showlog(SslError.SSL_EXPIRED+" ssl invalid");
                    break;
                case SslError.SSL_UNTRUSTED:
                    showlog(SslError.SSL_UNTRUSTED+" ssl untrusted");
                    break;
                case SslError.SSL_MAX_ERROR:
                    showlog(SslError.SSL_MAX_ERROR+" ssl max error");
                    break;
            }
            builder.setTitle("SSL证
                        
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/huaxun66/article/details/53666855
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-15 17:52:11
  • 阅读 ( 1525 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢