1、了解原生安卓访问Https接口
1.使用HttpsUrlConnection访问
进行Https请求时,需要把需要把HttpUrlConnection换成HttpsUrlConnection
HttpsUrlConnection与HttpUrlConnection相比多了两个方法
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、上述SSLSocketFactory是abstract class,我们使用sslContext.getSocketFactory()生成SSLSocketFactory
3、获得一个SSLContext实例并初始化
//指定一个协议名称获得一个对象,一般都是TLS协议
SSLContext context = SSLContext.getInstance("TLS");//经测试,TSLv1也可以,剩余v2、v3不可用,应该是安卓没有实现
//参数1:KeyManager[] km,管理客户端密钥库,可以为空
//参数2:TrustManager[] tm,管理信任证书,可以为空
//参数3:SecureRandom 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.1、Certificate是abstract 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;
}
除了上面的正规流程外,也可以在
//参数1:KeyManager[] km,管理客户端密钥库,可以为空
//参数2:TrustManager[] tm,管理信任证书,可以为空
//参数3:SecureRandom 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格式的密钥库里