Android平台实现双向证书(https)验证简单说明
16lz
2021-01-26
Android平台实现双向证书(https)验证简单说明
前言
- Android端实现双向认证的难点主要在于Android接受的证书格式是BKS,而一般提供的证书不包含此格式,需要手动转换
- 转换之后如果请求不了,表现为握手失败(Handshake failed),则一般是证书转换错误
- 网上有一大堆转换方法,有的虽能正常生成BKS证书,但还是导致握手失败,推测应该还是转换方式有误
- 在这里提供一种自己亲自试验过能正常转换的方法供大家参考
证书转换
使用转换工具
- 在这里提供一个比较好用的转换工具Portecle
- 下载完后解压双击start.bat即可使用,至于详细使用请自行百度
- 使用此工具可以很方便地将jks转为bks
通过openssl和keytool转换
- openssl可以到官网下载,openssl暂时没有windows版,大伙可以在此处下载Win32OpenSSLwindows版本
- 下载完记得配置环境变量,同时也记得配置jdk的环境变量,因为后面需要用到keytool命令
- 现在假设你拿到了客户端证书client.crt和client.key(或者client.pem和client.key)
- 通过以下命令生成pfx文件,期间会让输入密码,输入你想要的密码即可,假如这里输入的是123456
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.pem
或者
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
- 执行成功则可以看到client.pfx文件,接着再执行如下命令
keytool -importkeystore -srckeystore client.pfx -destkeystore client.jks -srcstoretype PKCS12 -deststoretype JKS
- 执行成功即可看到生成了client.jks
- 拿到生成的jks就可以通过Portecle工具转换成bks了
代码实现
SSLHelper.java
import android.content.Context;import java.io.IOException;import java.io.InputStream;import java.security.KeyManagementException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.UnrecoverableKeyException;import java.security.cert.CertificateException;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManagerFactory;public class SSLHelper { private static final String TAG = "SSLHelper"; private final static String CLIENT_PRI_KEY = "client.bks"; private final static String TRUSTSTORE_PUB_KEY = "ca.bks"; private final static String CLIENT_BKS_PASSWORD = "123456"; private final static String TRUSTSTORE_BKS_PASSWORD = "123456"; private final static String KEYSTORE_TYPE = "BKS"; private final static String PROTOCOL_TYPE = "TLS"; private final static String CERTIFICATE_STANDARD = "X509"; public static SSLSocketFactory getSSLCertifcation(Context context) { SSLSocketFactory sslSocketFactory = null; try { // 服务器端需要验证的客户端证书,其实就是客户端的keystore KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); // 客户端信任的服务器端证书 KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE); //读取证书 InputStream ksIn = context.getAssets().open(CLIENT_PRI_KEY); InputStream tsIn = context.getAssets().open(TRUSTSTORE_PUB_KEY); //加载证书 keyStore.load(ksIn, CLIENT_BKS_PASSWORD.toCharArray()); trustStore.load(tsIn, TRUSTSTORE_BKS_PASSWORD.toCharArray()); ksIn.close(); tsIn.close(); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(CERTIFICATE_STANDARD); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(CERTIFICATE_STANDARD); trustManagerFactory.init(trustStore); keyManagerFactory.init(keyStore, CLIENT_BKS_PASSWORD.toCharArray()); //初始化SSLContext SSLContext sslContext = SSLContext.getInstance(PROTOCOL_TYPE); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new java.security.SecureRandom()); sslSocketFactory = sslContext.getSocketFactory(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return sslSocketFactory; }}
- HttpsUtil.java
import java.io.IOException;import java.io.InputStream;import java.security.KeyManagementException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.UnrecoverableKeyException;import java.security.cert.CertificateException;import java.security.cert.CertificateFactory;import java.security.cert.X509Certificate;import javax.net.ssl.HostnameVerifier;import javax.net.ssl.KeyManager;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSession;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.TrustManagerFactory;import javax.net.ssl.X509TrustManager;public class HttpsUtil { public static class SSLParams { public SSLSocketFactory sSLSocketFactory; public X509TrustManager trustManager; } public static SSLParams getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password) { SSLParams sslParams = new SSLParams(); try { TrustManager[] trustManagers = prepareTrustManager(certificates); KeyManager[] keyManagers = prepareKeyManager(bksFile, password); SSLContext sslContext = SSLContext.getInstance("TLS"); X509TrustManager trustManager = null; if (trustManagers != null) { trustManager = new MyTrustManager(chooseTrustManager(trustManagers)); } else { trustManager = new UnSafeTrustManager(); } sslContext.init(keyManagers, new TrustManager[]{trustManager},null); sslParams.sSLSocketFactory = sslContext.getSocketFactory(); sslParams.trustManager = trustManager; return sslParams; } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } catch (KeyManagementException e) { throw new AssertionError(e); } catch (KeyStoreException e) { throw new AssertionError(e); } } public static class UnSafeHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } public static class UnSafeTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } private static TrustManager[] prepareTrustManager(InputStream... certificates) { if (certificates == null || certificates.length <= 0) return null; 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) { } } TrustManagerFactory trustManagerFactory = null; trustManagerFactory = TrustManagerFactory. getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); return trustManagers; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return null; } private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) { try { if (bksFile == null || password == null) return null; KeyStore clientKeyStore = KeyStore.getInstance("BKS"); clientKeyStore.load(bksFile, password.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(clientKeyStore, password.toCharArray()); return keyManagerFactory.getKeyManagers(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return null; } private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) { for (TrustManager trustManager : trustManagers) { if (trustManager instanceof X509TrustManager) { return (X509TrustManager) trustManager; } } return null; } private static class MyTrustManager implements X509TrustManager { private X509TrustManager defaultTrustManager; private X509TrustManager localTrustManager; public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException { TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); var4.init((KeyStore) null); defaultTrustManager = chooseTrustManager(var4.getTrustManagers()); this.localTrustManager = localTrustManager; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { defaultTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException ce) { localTrustManager.checkServerTrusted(chain, authType); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }
- retrofit调用
//创建日志拦截器,用于日志打印 HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //创建okhttp OkHttpClient httpClient = new OkHttpClient().newBuilder() .addInterceptor(interceptor) .sslSocketFactory(SSLHelper.getSSLCertifcation(this), new HttpsUtil.UnSafeTrustManager()) .hostnameVerifier(new HttpsUtil.UnSafeHostnameVerifier())//由于还没有域名,此处设置忽略掉域名校验 .build(); //创建retrofit String baseUrl = "https://180.101.147.89:8743/"; Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(httpClient) //.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();
- 顺便贴下依赖build.gradle
apply plugin: 'com.android.application'android {...}dependencies { ... //retrofit implementation "com.squareup.retrofit2:retrofit:2.3.0" implementation "com.squareup.retrofit2:converter-scalars:2.3.0" implementation "com.squareup.retrofit2:converter-gson:2.3.0" implementation "com.squareup.okhttp3:logging-interceptor:3.8.1" }
参考
- http://blog.csdn.net/zhangyong125/article/details/50402183
更多相关文章
- 基于Android移动终端的搜索客户端应用【团队项目】
- 微信Android客户端架构演进及其对开发流程的影响
- Android(安卓)Socket 发送与接收数据问题: 发送后的数据接收到总
- Android:学习AIDL,这一篇文章就够了(下)
- Android客户端实现图片轮播控件
- Android(安卓)签名
- Android与PHP交互,Android传递JSON数据,PHP接受并保存数据
- Android(安卓)面试必备 - http 与 https 协议
- Android中进程间通信(IPC)方式总结