Android 用自签名证书实现https请求
一.概要
为了保护用户的信息安全、保护自己的商业利益,减少攻击面,我们需要保障通信信道的安全,采用开发方便的HTTPS是比较好的方式,比用私有协议要好,省时省力。但是如果HTTPS使用不当,就很难起到应有的保护效果。例如有人为了省事选择信任所有证书的方式绕过证书的认证,这样https的作用就完全没意义了。下面我就用Android比较流行的网络框架okhttp来做使用https,包括单向认证和双向认证
二.OpenSSL生成证书
网上很多如何生成自签名证书的资料,这里就不详细说明,大概步骤如下:
1.生成私钥server.key
2.生成server.crt CA根证书(公钥)
3.用根证书给客户端签发证书server.cer
三.单向认证
把服务器颁发的证书server.cer放到Android的目录assets
/**
* 添加证书
*
*/
public static SSLSocketFactory getSocketFactory() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
InputStream is = mContext.getAssets().open("server.cer");
keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is));
if (is!=null){
is.close();
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
然后
mOkHttpClient.setSslSocketFactory(getSocketFactory());
就这样就实现了配置okhttp的https请求
另外如果后台服务器用是nginx搭建的可以先配置
ssl on;
ssl_certificate ...server.crt;
ssl_certificate_key ...server.key;
然后Android客户端直接使用server.crt来代替server.cer,利用nginx来认证https请求,省去了客户端到多台服务器的认证。
四.双向认证
首先对于双向证书验证,也就是说,客户端也会有个“.key文件”,服务器那边会同时有个“.cer文件”与之对应。
我们已经生成了server.key和server.cer文件。
接下来按照生成证书的方式,再生成一对这样的文件,我们命名为:client.key,client.cer.
然后配置服务器
<Connector 其他属性与前面一致
clientAuth="true"
truststoreFile="client.cer"
/>
配置Android端:
还记得我们单向认证时
sslContext.init(null, trustManagerFactory.getTrustManagers(),
new SecureRandom());
sslContext.init的第一个参数我们传入的是null,第一个参数的类型实际上是KeyManager[] km,主要就用于管理我们客户端的key。
另外我们生成的客户端client.key是jks格式的文件,java平台才能识别,Android直接用会报错:Java.io.IOException: Wrong version of key store。所有Android端要转成bks格式,可以用转换工具Portecle,网上很多资料可以参考,这里就不详说了。
然后就是我们Android端的代码实现:
/**
* 添加证书
*
*/
public static SSLSocketFactory getSocketFactory() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
InputStream is = mContext.getAssets().open("server.cer");
keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is));
if (is!=null){
is.close();
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(mContext.getAssets().open("client.bks"), "123456".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
"123456"为证书密码
总结
由于项目需要用到https,而之前也没有接触过,花了几天搜罗了很多资料,最后找到了实践可行的的方法,现在分享出来,希望对大家有帮助,如果有理解错误的地方,请指出,谢谢!