android https访问

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jackzhouyu/article/details/79978749

https的握手过程

https在传输真正的数据之前,需要客户端和服务端进行一次协议握手,主要配置好两边的私钥和一些初始化工作,大致流程如下图:

这里写图片描述

使用中遇到的问题

最近项目需要使用https方式访问,项目是用retrofit+okhttp框架,需要把以前的http全部改为https访问;关于https访问配置的方式有三种

  • 信任所有证书
  • 验证某一个特定证书

信任所有证书

直接将以前的http改成https即可,不需要做任何改变,照理说第一种改变https即可,但是我却出现这种异常

Suppressed: javax.net.ssl.SSLHandshakeException: Handshake failed
04-17 18:09:07.892 3160-3160/com.chinamobile.iot.easiercharger W/System.err: 		... 36 more
04-17 18:09:07.892 3160-3160/com.chinamobile.iot.easiercharger W/System.err: 	Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x9e23da80: Failure in SSL library, usually a protocol error
04-17 18:09:07.892 3160-3160/com.chinamobile.iot.easiercharger W/System.err: error:100bd10c:SSL routines:ssl3_get_record:WRONG_VERSION_NUMBER (external/boringssl/src/ssl/s3_pkt.c:311 0xa92997f7:0x00000000)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err:     at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:353)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err: 		... 35 more
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err: Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x9e23da80: Failure in SSL library, usually a protocol error
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err: error:100bd10c:SSL routines:ssl3_get_record:WRONG_VERSION_NUMBER (external/boringssl/src/ssl/s3_pkt.c:311 0xa92997f7:0x00000000)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err:     at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:353)
04-17 18:09:07.902 3160-3160/com.chinamobile.iot.easiercharger W/System.err: 	... 35 more

这种问题原因有多重,可能证书有问题,也可能是自己的代码有问题;我的项目出现这个问题的原因是:

未改变https之前,我的网络接口类似于这种

http://www.power.com:80

改成https后是这种

https://www.power.com:80

改了之后就出现上诉的错误,你们能看出问题所在吗?


那就是端口号


http协议默认使用的端口号是80,https默认的端口是443,而这里我改动https后端口号没变,所以这个域名是访问不通的,除非https通信端口号被改成了80才会通,解决办法就是端口号改成443即可解决

这是我使用第一种方式引发的问题,如果使用自签名的证书或者忽略所有证书,一旦你的端口号有误,也可能出现上诉问题,伙伴们,擦亮眼睛吧!

信任某一特定证书

获取证书

可以从网站上导出证书,具体方法可点击参考,导出比较简单,重要的是格式,博主在导出的.cer格式的证书后,反正asstes目录下,最后以stream方式打开,但是在okhttp使用这个证书文件验证时总出错,报错信息大致是code编码问题,思考了良久和查询了资料,最后确定原因是证书用在okhttp框架里去的时候要永城utf-8格式,以下是解决方案:

  1. 将cer文件里面的key原封不等的拷贝出来
public final static String SSL_KEY = "-----BEGIN CERTIFICATE-----\n" +
            "Fw0xODAxMTYwMDAwMDBaFw0yMDAyMTUxMjAwMDBaMIGdMQswCQYDVQQGEwJDTjEQ\n" +
            "MA4GA1UEBxMHQ2hlbmdkdTE4MDYGA1UEChMvQ2hpbmEgTW9iaWxlIElPVCBDb21w\n" +
            "YW55IExpbWl0ZWQgQ2hlbmdkdSBicmFuY2gxJjAkBgNVBAsTHUluZm9ybWF0aW9u\n" +
            "IFRlY2hub2xvZ3kgQ2VudGVyMRowGAYDVQQDExF3d3cudGF4aWFpZGVzLmNvbTCC\n" +
            "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOryUl/OGkrUkqSzoimarOPv\n" +
            "V0qQ7EsyS1ny0UZ7jcxnFS7ztOLh/0XIaPvX4e2KWgdcgxL8LbQ/gFKeRr5s6Uub\n" +
            "QUeczE9+CO4ic5opzS76QVJVH0kTSBoB1HBJ0TAV3XhSt+SOF7T5bpJrcCdijw7X\n" +
            "-----END CERTIFICATE-----";
  1. 导入时要进行一次utf编码
InputStream[] key = new InputStream[]{new Buffer().writeUtf8(SSL_KEY_TEST).inputStream()};
  1. 进行证书验证
public static SSLParams setCertificates(InputStream... certificates)
    {
        SSLParams sslParams = new SSLParams();
        try
        {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates)
            {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

                try
                {
                    if (certificate != null){
                        certificate.close();
                    }
                } catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");

            TrustManagerFactory trustManagerFactory =
                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            trustManagerFactory.init(keyStore);
            sslContext.init
                    (
                            null,
                            trustManagerFactory.getTrustManagers(),
                            new SecureRandom()
                    );

            sslParams.trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
            sslParams.sSLSocketFactory = sslContext.getSocketFactory();
            return sslParams;
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        return sslParams;
    }

成功后,可以去换一个错误的证书测试一下,不要尝试着去修改正确证书里面的内容,这样会造成第三步骤里面证书验证抛出异常,导致所有证书这块代码都没执行,信任所有证书;建议去其他https网站,按照上面步骤导出一个第三方的cer证书,最后用错误的证书测试会有以下几个字眼,就表示成功;

 Trust anchor for certification path not found.

服务端验证

以上讲述的都是客户端验证服务端的证书,客户端只包含了服务端的公钥;而双向验证则会多了几点,详细请看下:

  1. 客户端发送自己的SSL版本信息、支持加密算法等信息
  2. 服务端收到后发送SSL版本信息、支持加密算法等信息,并且发送自己的公钥证书给客户端
  3. 客户端会验证公钥证书的合法性,CA机构颁布、过期或者是否信任
  4. 如果不通过将会终止
  5. 服务端会要求客户端发送客户端自己的公钥证书给服务端,收到后,服务端会对其证书进行验证
  6. 验证通过后,会获得客户端的公钥;至此,客户端有自己的私钥和服务端的公钥;服务端有自己的私钥和客户端的公钥
  7. 客户端会发送自己所支持的对称加密方案,服务端会选择一个加密程度最高的算法
  8. 选择后,会用客户端的公钥加密选择的方案,加密后发送给客户端
  9. 客户端用自己的私钥进行解密,并使用该加密方案产生初始的加密秘钥
  10. 客户端使用服务端的公钥加密该对称私钥,发送给服务端
  11. 服务端收到加密对称私钥后,用自己的私钥解密后会获得对称秘钥,
  12. 最后,两边都获得了对称秘钥,使用该秘钥进行通信
    单向认证没有服务端对客户端的证书验证,并且在第8步骤是明文发送加密方案

代码完成

	//客户端秘钥keystore
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
    //读取客户端私钥
    InputStream ksIn = context.getAssets().open(CLIENT_PRI_KEY);
    //keystore加载秘钥和密码
    keyStore.load(ksIn, CLIENT_BKS_PASSWORD.toCharArray());
    ksIn.close();
    //初始化SSLContext
    SSLContext sslContext = SSLContext.getInstance(“TLS”);
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X.509");
    
    keyManagerFactory.init(keyStore, CLIENT_BKS_PASSWORD.toCharArray());
    //第一个参数向服务端发起验证 第二个用于验证服务端的证书
    sslContext.init(keyManagerFactory.getKeyManagers(), null, null); 
 
    sslSocketFactory = sslContext.getSocketFactory();

总结

证书验证

  • 获取证书管理类CertificateFactory和KeyStore
  • 使用证书管理类CertificateFactory和证书初始化Keystore
  • 获取证书信任管理类TrustManagerFactory,将Keystore里面的证书添加到证书信任管理类中
  • SSL上下文类SSLContext用证书信任管理类进行初始化
  • 将SSL添加到okhttpbuilder里面即可

猜你喜欢

转载自blog.csdn.net/jackzhouyu/article/details/79978749