原生安卓访问Https单向认证和双向认证接口

1、了解原生安卓访问Https接口

1.使用HttpsUrlConnection访问

进行Https请求时,需要把需要把HttpUrlConnection换成HttpsUrlConnection

HttpsUrlConnectionHttpUrlConnection相比多了两个方法

setHostnameVerifier(HostnameVerifier v);//验证主机名称

setSSLSocketFactory(SSLSocketFactory sf);//验证证书

 

2.验证服务端证书

Android手机有一套共享证书的机制如果目标 URL 服务器下发的证书不在已信任的证书列表里,或者该证书是自签名的,不是由权威机构颁发,那么就会报异常:

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

提示我们未验证证书,需要使用上述的HttpUrlConnection.setSSLSocketFactory(SSLSocketFactory sf)方法验证服务端证书

如果是使用其他的网络请求三方库,则亦需要使用类似此功能的方法。

 

3.验证主机名称

Https请求需要同时验证服务端证书和主机名称,如果只做了证书验证,则会报异常:

”java.io.IOException: Hostname '172.17.122.173' was not verified”

提示我们主机名称没有验证,需要使用上述的setHostnameVerifier(HostnameVerifier v)方法验证主机名称

 

4.双向认证

双向认证需要在前面的基础上加上客户端密钥库

二、验证主机名称

httpsUrlConnection.setHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String s, SSLSession sslSession) {
        return true;//通过所有主机名称
    }
});

自定义HostnameVerifier,简单的话就是根据域名进行字符串匹配校验;业务复杂的话,还可以结合配置中心、白名单、黑名单、正则匹配等多级别动态校验;总体来说逻辑还是比较简单的,反正只要正确地实现那个方法。

三、单向认证

1、我们使用httpsUrlConnection.setSSLSocketFactory(SSLSocketFactory sf)验证服务端证书

2、上述SSLSocketFactoryabstract class,我们使用sslContext.getSocketFactory()生成SSLSocketFactory

3、获得一个SSLContext实例并初始化

//指定一个协议名称获得一个对象,一般都是TLS协议
    SSLContext context = SSLContext.getInstance("TLS");//经测试,TSLv1也可以,剩余v2v3不可用,应该是安卓没有实现

//参数1KeyManager[] km,管理客户端密钥库,可以为空

//参数2TrustManager[] tm,管理信任证书,可以为空

//参数3SecureRandom sr,随机数,可以为空

//因为这里是单向认证,所以参数1置为NULL
    context.init(null, TrustManager[] tm, new SecureRandom());

4、上述TrustManager是一个interface,我们使用trustManagerFactory..getTrustManagers()生成TrustManager[]

5、获得一个TrustManagerFactory实例

//TrustManagerFactory需要指定一个管理算法来生成一个对象

//默认是PKIX,这里我们不探究能使用哪些算法
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);

6、上述KeyStore的构造方法为protected,我们使用KeyStore.getInstance(String type)获得一个KeyStore实例(这里简单列举两种情况)

6.1、文件为.cer/crt的证书文件

//生成一个指定密钥库格式的密钥库对象,安卓默认格式为BKS

String keyStoreType = KeyStore.getDefaultType();

//如果在初始化时要用到另一个密钥库的话,密钥库格式需要和另一个密钥库格式相同。因为这里只需要导入信任证书,所以设置为默认格式

KeyStore keyStore = KeyStore.getInstance(keyStoreType );

//等同于-imporkeystore命令,导入另一个密钥库的内容来完成初始化

//参数1:另一个密钥库的流,参数2:另一个密钥库的密码

//必须进行这一步来完成初始化,因为这里是单独的证书,所以都置为NULL

keyStore.load(null, null);

//添加证书到信任列表,并指定别名

keyStore.setCertificateEntry("mykey", certificate);

6.1.1Certificateabstract class,我们使用CertificateFactory.generateCertificate(InputStream inStream)来获得一个Certificate实例

6.1.2、获得一个CertificateFactory实例

//生成一个指定证书标准的CertificateFactory,用从流里生成指定证书标准的证书

//X.509是一种被广泛使用的证书标准

CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

6.1.3、获得证书文件的InputStream

方便起见,我们把证书文件放在assets文件夹下

InputStream inputStream = getAssets().open("xxx.cer");

6.2、文件为已添加信任证书的密钥库,这里以BKS密钥库格式为例

//因为所导入密钥库的格式为BKS格式,所以这里设置为BKS

KeyStore keyStore = KeyStore.getInstance(BKS);

keyStore.load(getAssets().open("xxx.bks"), "123456".toCharArray());

 

下面提供上述两种情况的代码实现:

文件为.cer/crt的证书文件:

        try {

//1.生成证书对象

            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            InputStream inputStream = getAssets().open("xxx.cer");

            Certificate certificate = certificateFactory.generateCertificate(inputStream);

            inputStream.close();

//2.生成KeyStore对象,导入信任证书

            String keyStoreType = KeyStore.getDefaultType();

            KeyStore keyStore = KeyStore.getInstance(keyStoreType);

            keyStore.load(null, null);

            keyStore.setCertificateEntry("mykey", certificate);

//3.使用KeyStore初始化TrustManagerFactory

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);

            tmf.init(keyStore);

//4.使用TrustManagerFactory初始化SSLContext

            SSLContext context = SSLContext.getInstance("TLSv1");

            context.init(null, tmf.getTrustManagers(), new SecureRandom());

//5.使用SSLContext得到一个SSLSocketFactory对象

            urlConnection.setSSLSocketFactory(context.getSocketFactory());

        } catch (Exception e) {

            throw e;

        }

 

文件为已添加信任证书的密钥库,这里以BKS密钥库格式美丽:

        try {

//1.导入以添加信任证书的密钥库,

            KeyStore keyStore = KeyStore.getInstance(BKS);

            keyStore.load(getAssets().open("xxx.bks"), "123456".toCharArray());

//2.使用KeyStore初始化TrustManagerFactory

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);

            tmf.init(keyStore);

//3.使用TrustManagerFactory初始化SSLContext

            SSLContext context = SSLContext.getInstance("TLSv1");

            context.init(null, tmf.getTrustManagers(), new SecureRandom());

//4.使用SSLContext得到一个SSLSocketFactory对象

            urlConnection.setSSLSocketFactory(context.getSocketFactory());

        } catch (Exception e) {

            throw e;

        }

 

除了上面的正规流程外,也可以在

//参数1KeyManager[] km,管理客户端密钥库,可以为空

//参数2TrustManager[] tm,管理信任证书,可以为空

//参数3SecureRandom sr,随机数,可以为空

//因为这里是单向认证,所以参数1置为NULL
    context.init(null, TrustManager[] tm, new SecureRandom());

这一个函数这里,自定义TrustManager来验证服务端证书,具体代码如下:

        try {

//1.生成证书对象,如果在后面的自定义TrustManager里信任所有证书的话,可忽略

            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

            InputStream inputStream = getAssets().open("309.cer");

          final Certificate certificate = certificateFactory.generateCertificate(inputStream);

            inputStream.close();

//2.使用自定义TrustManager初始化SSLContext

            SSLContext context = SSLContext.getInstance("TLS");

            context.init(null, new TrustManager[]{new X509TrustManager() {

                @Override

                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

//do nothing,接受任意客户端证书

                }

                @Override

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

//do nothing,接受任意服务端证书。这里我们试着验证下服务端证书

                    for (X509Certificate cer : chain) {

                        try {

                            cer.verify(certificate.getPublicKey());

                        } catch (Exception e) {

                            e.printStackTrace();

                        }

                    }

                }

//返回一个非空(大小可以为0)的CA颁发者证书数组

                @Override

                public X509Certificate[] getAcceptedIssuers() {

                    return new X509Certificate[0];

                }

            }}, new SecureRandom());

//3.使用SSLContext得到一个SSLSocketFactory对象

            urlConnection.setSSLSocketFactory(context.getSocketFactory());

        } catch (Exception e) {

            throw e;

        }

四、双向认证

双向认证在单向认证的基础上,添加客户端密钥库

1、我们使用sslContext.init(KeyManager[] km, TrustManager[] tm, new SecureRandom())函数的第一个参数来添加客户端密钥库

2、上述KeyManager是一个interface,我们使用keyManagerFactory..getKeyManagers()生成KeyManager[]

3、获得一个KeyManagerFactory实例

//KeyManagerFactory需要指定一个管理算法来生成一个对象

//默认是PKIX,这里我们不探究能使用哪些算法
    String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(tmfAlgorithm);

//TrustManagerFactory的初始化不同,这里需要输入密钥对的密码,如果是没用密钥对密码的密钥库格式,则这里可以随便填
    kmf.init(keyStore, 123456.toCharArray());

4、上述KeyStore的构造方法为protected,我们使用KeyStore.getInstance(String type)获得一个KeyStore实例(这里简单列举两种情况)

4.1、文件为非BKS密钥库格式的密钥库,这里以PKCS12为例

密钥库密码为123456

//如果在初始化时要用到另一个密钥库的话,密钥库格式需要和另一个密钥库格式相同

KeyStore keyStore = KeyStore.getInstance(PKCS12);

//等同于-imporkeystore命令,导入另一个密钥库的内容来完成初始化

//参数1:另一个密钥库的流,参数2:另一个密钥库的密码

//必须进行这一步来完成初始化。为了方便起见,这里我们将所需密钥库放在了assets文件夹下

keyStore.load(getAssets().open("XXX.p12"), 123456.toCharArray());

注:PKCS12没有密钥对的保护密码,所以在后续的keyManagerFactory.init()函数里可以随意设置参数2的内容

4.2、文件为BKS密钥库格式的密钥库

密钥库密码为123456,密钥对密码为654321

KeyStore keyStore = KeyStore.getInstance(BKS);

keyStore.load(getAssets().open("xxx.bks"), "123456".toCharArray());

注:BKS有密钥对的保护密码,所以在后续的keyManagerFactory.init()函数里需要设置参数2的内容,kmf.init(keyStore, 654321.toCharArray());

 

下面提供上述两种情况的代码实现:

文件为非BKS密钥库格式的密钥库,这里以PKCS12为例:

        try {

//1.读取客户端密钥库的信息,生成客户端KeyStore对象

            KeyStore keyStore = KeyStore.getInstance(PKCS12);

            keyStore.load(getAssets().open("xxx.p12"), "123456".toCharArray());

//2.使用客户端KeyStore初始化一个KeyManagerFactory

            String tmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();

            keyManagerFactory tkmf = keyManagerFactory.getInstance(tmfAlgorithm);

            kmf.init(keyStore, asdsadasds.toCharArray());

//3.使用KeyManagerFactory和自定义TrustManager来生成SSLContext对象

            SSLContext context = SSLContext.getInstance("TLS");

            context.init(kmf.getKeyManagers(), new TrustManagers[]{new myTrustManager()}, new SecureRandom());

//4.使用SSLContext得到一个SSLSocketFactory对象

            urlConnection.setSSLSocketFactory(context.getSocketFactory());

        } catch (Exception e) {

            throw e;

        }

 

文件为BKS密钥库格式的密钥库:

        try {

//1.读取客户端密钥库的信息,生成客户端KeyStore对象

            KeyStore keyStore = KeyStore.getInstance(BKS);

            keyStore.load(getAssets().open("xxx.bks"), "123456".toCharArray());

//2.使用客户端KeyStore初始化一个KeyManagerFactory

            String tmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();

            keyManagerFactory tkmf = keyManagerFactory.getInstance(tmfAlgorithm);

            kmf.init(keyStore, 654321.toCharArray());

//3.使用KeyManagerFactory和自定义TrustManager来生成SSLContext对象

            SSLContext context = SSLContext.getInstance("TLS");

            context.init(kmf.getKeyManagers(), new TrustManagers[]{new myTrustManager()}, new SecureRandom());

//4.使用SSLContext得到一个SSLSocketFactory对象

            urlConnection.setSSLSocketFactory(context.getSocketFactory());

        } catch (Exception e) {

            throw e;

        }

五、注意

1.使用HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory)静态方法,设置HttpsURLConnection验证证书所需socketFactory的默认值,免去多次生成证书验证的过程

2.BKS密钥库格式保存的证书或者密钥库,访问速度比BKS格式的密钥库要慢很多,建议服务端证书和客户端密钥库都保存在BKS格式的密钥库里

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值