相信很多客户端开发人员特别是Android开发人员对https如何在程序中使用存有疑惑,项目中有的说“什么都不用校验校验都在服务端”、“ios都不用校验,系统自带处理机制”……之类的,不管是使用Volley、OkHttp还是其他网络框架,不做校验或使用不安全校验确实能是客户端正常访问https的接口服务地址,但是如此以来给app特别是Android版本的应用程序带来了极大安全隐患。
网上查找解决方案,千篇一律,思路都说的很好,具体怎么实现呢?
下面是个人对Android实现https配置的经验及代码,分享学习,仅做参考,有问题请留言。
客户端https认证方式
- 信任所有——不做处理,没有起到校验作用,有风险
- 单向认证——自定义信任规则,校验服务端证书
- 单向认证——使用预埋证书,校验服务端证书(自签名证书)
双向认证——使用bks证书和密码管理客户端证书(双向认证),使用预埋证书,校验服务端证书(自签名证书)
网上查到大多数代码类似如下,此种方式即为不做校验处理,风险很高:
/**
* 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
* 这是一种有很大安全漏洞的办法
*/
public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//TODO 此处应该加入自定义的校验规则
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
};
// 创建TLS类型的SSLContext对象, that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{manager}, null);
//下面以OkHttp设置https为例,其他网络框架一样设置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();
下面试我的实现方式:单向认证——自定义信任规则,校验服务端证书+使用预埋证书校验服务端证书公钥有效性
/**
* 自定义校验规则:1、校验服务端证书有效性;2、使用本地预埋证书校验服务端证书公钥(需要提前把服务端cer证书放入Android功能的assets目录下,此种方式要考虑都证书有效期,如果过期需要app端及时替换证书——更具自身情况可以去掉预埋证书key校验代码)
*/
private class SafeTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
LogUtils.d("checkServerTrusted 校验服务端证书");
if (chain == null) {
throw new IllegalArgumentException("Check server X509Certificate is null");
}
if (chain.length < 0) {
throw new IllegalArgumentException("Check server X509Certificate is empty");
}
//读取本地预埋服务端证书获取其publicKey与服务中携带的key比对
InputStream is_local = null;
try {
is_local = new BufferedInputStream(getAssets().open("替换为自己的证书名称.cer"));//提前把证书放入assets中
} catch (IOException e) {
e.printStackTrace();
LogUtils.e(e.getMessage());
}
X509Certificate serverCert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is_local);
for (X509Certificate certificate : chain) {
// 检查证书是否过期,签名是否通过等
certificate.checkValidity();
// 校验服务端证书公钥,与app预埋证书公钥作比对
try {
LogUtils.i("publicKey==" + serverCert.getPublicKey());
certificate.verify(serverCert.getPublicKey());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
LogUtils.e(e.getMessage());
} catch (InvalidKeyException e) {
e.printStackTrace();
LogUtils.e(e.getMessage());
} catch (NoSuchProviderException e) {
e.printStackTrace();
LogUtils.e(e.getMessage());
} catch (SignatureException e) {
e.printStackTrace();
LogUtils.e(e.getMessage());
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}//自定义trustManager完成
// 创建TLS类型的SSLContext对象, that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new SafeTrustManager()}, null);
//下面以OkHttp设置https为例,其他网络框架一样设置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();
如此就实现了自定义的trustManager校验规则,还是需要注意预埋证书是有有效期限制的,请慎用,一旦证书失效会导致服务请求不通——建议app添加版本更新接口,而且该接口不要使用强校验的https请求,使用普通请求,如果证书失效至少能保证版本更新接口正常访问便于用户更新新的apk版本。
另外,配置完校验规则,如果使用阿里聚安全等加固检测网站检测经常会报https hostname校验的高危漏洞,所以最好再添加对服务域名的强校验,而不是采用信任所有地址的方式(builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);//此写法等于没有做校验)
下面是我的校验方式:
/**
* 验证服务端证书域名是否匹配——有可能与服务请求地址不一致
*/
private class SafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
//注意此处有可能与服务请求地址域名不一致,请于申请服务端证书的人员确认申请域名
LogUtils.d("校验服务域名");
return hostname.equals("*ver.zp.com");
// return true;
}
}
//以OkHttp为例设置方式——使用服务域名强校验
builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
builder.hostnameVerifier(new SafeHostnameVerifier());
总结为下:
客户端实现https配置:添加自定义校验规则、校验服务域名
更多关于Android https的问题及HttpUrlConnection使用https方式 请移步:
http://blog.csdn.net/u011084603/article/details/73873677
关于webView中设置Https的方法可移步:http://www.cnblogs.com/liyiran/p/7011317.html
获取证书两种方法
- 服务器组直接给。如测试 12306 网站的时候,进入网页,12306 会提供根证书的下载
- 通过网页获取。 Chrome 浏览器,按 F12 选择 Security 选择 Certificate Error 的 View
certificate 在弹出的证书,选择详细信息
在详细信息页面,点击复制到文件,一路下一步,然后选择保存证书的地方,就把证书成功导出来了。
证书的读取
从 assert 中读取文件
InputStream is = getAssets().open(“root.cer”);