转载自:
https://blog.csdn.net/u013768203/article/details/72874242
最近来了家新公司,后台设计在非线上环境用自签名证书,线上环境用CA证书,然后发了份.cer公钥给我.让我在客户端处理一下.
我查了很多博客,只言片语的,
HTTPS的流程也比较长,
今天调试好了,贴出连续的代码给大家看一下.
https有2种情况 单向验证和双向验证
单向认证:客户端通过直接读取后台给的公钥验证握手
比如直接读取cer文件或者直接把公钥写在代码里.
双向认证:客户的有公钥,后台也有公钥,互相存储对方的公钥,验证网络通讯,
这个时候Android端要生成bks.
有很多博客一上来就生成bks,一定要知道什么场景.
而我现在讨论单向验证,
基于鸿洋大神的这篇做代码补充:
Android Https相关完全解析 当OkHttp遇到Https
单向验证方法一:简单粗暴,直接信任所有.
在MainActicity.java中: 有个网络请求:loadData()方法
public void loadData() {
SSLSocketFactoryUtils.MyX509TrustManager myX509TrustManager= new SSLSocketFactoryUtils.MyX509TrustManager();
OkHttpClient okhttpclient=new OkHttpClient.Builder()
.sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(), SSLSocketFactoryUtils.createTrustAllManager())
.hostnameVerifier(new SSLSocketFactoryUtils.TrustAllHostnameVerifier())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://gw.dev.cmrh.com:8888/RH_MAS/")
.client(okhttpclient)
.addConverterFactory(
new Converter.Factory(){
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody value) throws IOException {
return value.string();
}
};
}
})
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<String> call = service.listRepos();
call.enqueue(MainActivity.this);
}
@Override
public void onResponse(Call<String> call, Response<String> response) {
Log.d("body-onResponse",response.body()+";;;;;;");
}
@Override
public void onFailure(Call<String> call, Throwable t) {
t.printStackTrace();
Log.d("body-onFailure",call.request().url()+"============");
}
关键的就是OkHttpClient里的 sslSocketFactory 和 hostnameVerifier设置
然后就是 Retrofit.client(okhttpclient)使用我们自己设置的client.
SSLSocketFactoryUtils.java的实现内容:
public class SSLSocketFactoryUtils {
/*
* 默认信任所有的证书
* todo 最好加上证书认证,主流App都有自己的证书
* */
public static SSLSocketFactory createSSLSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
}
return sslSocketFactory;
}
public static X509TrustManager createTrustAllManager() {
X509TrustManager tm = null;
try {
tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意客户端证书
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意服务端证书
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
} catch (Exception e) {
}
return tm;
}
public static class TrustAllHostnameVerifier implements HostnameVerifier{
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
这样就完成了.
这个方法简单粗暴,完全不用加任何公钥文件,直接信任所有,安全性当然降低.
哪你会问,后台的cer文件没有用到啊?
是的,接下来,介绍验证cer文件的代码:
第一步:公钥准备:
12306网站上直接提供他们的公钥下载,下下来的文件名srca.cer拿他做错误的公钥,
我们后台的公钥:ca.cer
将他们放入raw文件夹下.
在上面MainActivity中唯一改动的地方就是:
只有SSLSocketFactory的获取方式变了.
下面是SSLSocketFactoryUtils的内容:
//TODO 下面为新
static int keyServerStroreID =R.raw.ca;
public static SSLSocketFactory createSSLSocketFactory(Context context) {
SSLSocketFactory mSSLSocketFactory = null;
if(mSSLSocketFactory==null){
synchronized (SSLSocketFactoryUtils.class) {
if(mSSLSocketFactory==null){
InputStream trustStream = context.getResources().openRawResource(keyServerStroreID);
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
} catch (NoSuchAlgorithmException e) {
Log.e("httpDebug","createSingleSSLSocketFactory",e);
return null;
}
//获得服务器端证书
TrustManager[] turstManager = getTurstManager(trustStream);
//初始化ssl证书库
try {
sslContext.init(null,turstManager,new SecureRandom());
} catch (KeyManagementException e) {
Log.e("httpDebug","createSingleSSLSocketFactory",e);
}
//获得sslSocketFactory
mSSLSocketFactory=sslContext.getSocketFactory();
}
}
}
return mSSLSocketFactory;
}
/**获得指定流中的服务器端证书库*/
public static TrustManager[] getTurstManager(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null,null);
int index = 0;
for (InputStream certificate : certificates) {
if (certificate == null) {
continue;
}
Certificate certificate1;
try {
certificate1 = certificateFactory.generateCertificate(certificate);
}finally {
certificate.close();
}
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias,certificate1);
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
return trustManagerFactory.getTrustManagers();
} catch (Exception e) {
Log.e("httpDebug","SSLSocketFactoryUtils",e);
}
return getTurstAllManager();
}
/**
* 获得信任所有服务器端证书库
* */
public static TrustManager[] getTurstAllManager() {
return new X509TrustManager[] { new MyX509TrustManager() };
}
public static class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
System.out.println("cert: " + chain[0].toString() + ", authType: " + authType);
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
验证一下:
当static int keyServerStroreID =R.raw.ca;用我们自己的证书申请的效果是:
当static int keyServerStroreID =R.raw.srca;用12306访问公司接口
这样就达到我们的目的了.
最后.加个 GitHubService的内容吧
public interface GitHubService {
@GET("base/v1.0/downloadPage?appId=moa")
Call<String> listRepos();
}
MyApplication
public class MyApplication extends Application{
public static Context context;
@Override
public void onCreate() {
super.onCreate();
context=this.getApplicationContext();
}
}
这篇文章并没有太多https的讨论,只是简单的贴出了https在retrofit网络请求下的实现代码.
抛砖引玉,至少没做过这方面的同学可以很快上手.
补充一下读取 cer文件信息,特别是cer文件的有效时间:
import android.content.Context;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import junit.framework.TestCase;
public class CertManager extends TestCase{
/***
* 读取*.cer公钥证书文件, 获取公钥证书信息
* @author xgh
*/
public static void testReadX509CerFile(Context context) throws Exception{
try {
// 读取证书文件
File file = new File("src/GYGSCB2100000500.cer");
InputStream inStream = new FileInputStream(file);
// 创建X509工厂类
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//CertificateFactory cf = CertificateFactory.getInstance("X509");
// 创建证书对象
X509Certificate oCert = (X509Certificate) cf
.generateCertificate(inStream);
inStream.close();
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd");
String info = null;
// 获得证书版本
info = String.valueOf(oCert.getVersion());
System.out.println("证书版本:" + info);
// 获得证书序列号
info = oCert.getSerialNumber().toString(16);
System.out.println("证书序列号:" + info);
// 获得证书有效期
Date beforedate = oCert.getNotBefore();
info = dateformat.format(beforedate);
System.out.println("证书生效日期:" + info);
Date afterdate = oCert.getNotAfter();
info = dateformat.format(afterdate);
System.out.println("证书失效日期:" + info);
// 获得证书主体信息
info = oCert.getSubjectDN().getName();
System.out.println("证书拥有者:" + info);
// 获得证书颁发者信息
info = oCert.getIssuerDN().getName();
System.out.println("证书颁发者:" + info);
// 获得证书签名算法名称
info = oCert.getSigAlgName();
System.out.println("证书签名算法:" + info);
} catch (Exception e) {
System.out.println("解析证书出错!");
e.printStackTrace();
}
}
}
证书内容12306的确实可以用keytool 直接读取,但是我们公司的加密了,直接读取不了
————————————————
版权声明:本文为CSDN博主「丑丑鱼1992」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013768203/article/details/72874242