现在大部分APP内部都做有检查更新功能,如果用HTTP很容易被开发商劫持,所以我们来看一下Android 用HTTPS怎么做。
我刚开始做的时候,由于服务端给我的证书是非认证机构颁发的 (例如12306)或者自签名证书,那么我们是无法直接访问到服务器的,直接访问通常会抛出如下异常:
网上很多解决SSLHandshakeException异常的方案是自定义TrustManager忽略证书校验。
例如:
我用的网络请求框架是okhttp3
1、首先要拿到服务端生成的ce证书和密码
2、配置到自己的项目里,比如raw文件下
3、关键代码
public class SSLSocketFactoryEx extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactoryEx(KeyStore truststore) throws NoSuchAlgorithmException,
KeyManagementException, KeyStoreException,
UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port,
autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
private OkHttpClient mClient;//OKHttpClient;
InputStream inputStream = App.sContext.getResources().openRawResource(R.raw.tomcat);//证书文件名
HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(null,inputStream,
"服务端给的密码,");
mClient = new OkHttpClient.Builder()
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//直接返回true 即验证成功,即忽略认证
return true;
}
})
.build();
OkHttpUtils.initClient(mClient);
Okhttp绑定证书实现HTTPS单项认证
对于上述情况中存在的安全隐患,我们应该如何应对?最简单的解决方案就是在客户端内置服务器的证书,我们在校验服务端证书的时候只比对和App内置的证书是否完全相同,如果不同则断开连接。那么此时再遭遇中间人攻击劫持我们的请求时由于黑客服务器没有相应的证书,此时HTTPS请求校验不通过,则无法与黑客的服务器建立起连接。
那么接下来我们就结合Retrofit以访问12306为例来实现HTTPS的单项认证。
首先从12306网站下载签名证书,并放置到我们项目资源目录raw下。然后根据证书构造SSLSocketFactory,代码如下:
/**
* 单项认证
*/
publicstaticSSLSocketFactorygetSSLSocketFactoryForOneWay(InputStream... certificates){
try{
CertificateFactory certificateFactory = CertificateFactory.getInstance(CLIENT_TRUST_MANAGER, CLIENT_TRUST_PROVIDER);
KeyStore keyStore = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
keyStore.load(null);
intindex =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(CLIENT_AGREEMENT);
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(),newSecureRandom());
returnsslContext.getSocketFactory();
}catch(Exception e) {
e.printStackTrace();
}
returnnull;
}
接下来为OKHttpClient设置SslSocketFactory以及hostnameVerifier,代码如下:
InputStream certificate12306 = Utils.getContext().getResources().openRawResource(R.raw.srca);
OkHttpClient okHttpClient =newOkHttpClient.Builder()
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor)
.addInterceptor(newHttpHeaderInterceptor())
.addNetworkInterceptor(newHttpCacheInterceptor())
.sslSocketFactory(SslContextFactory.getSSLSocketFactoryForOneWay(certificate12306))
.hostnameVerifier(newSafeHostnameVerifier())
.cache(cache)
.build();
上述代码中hostnameVerifier是对服务器的校验,SafeHostnameVerifier代码如下:
privateclassSafeHostnameVerifierimplementsHostnameVerifier{
@Override
publicbooleanverify(String hostname, SSLSession session){
if(Constants.IP.equals(hostname)) {//校验hostname是否正确,如果正确则建立连接
returntrue;
}
returnfalse;
}
}
verify方法中对比了请求的IP和服务器的IP是否一致,一致则返回true表示校验通过,否则返回false,检验不通过,断开连接。对于网上有些处理是直接返回true,即不对请求的服务器IP做校验,我们不推荐这样使用。而且现在谷歌应用商店已经对此种做法做了限制,禁止在verify方法中直接返回true的App上线。
网友评论