您的位置:首页 > 理论基础 > 计算机网络

Android 偶遇HTTPS

2016-05-11 22:29 267 查看
原文地址

HTTPS ,该来的总要来的。

最近领导对移动端开发提出了很多优化的要求啊!其中一点就是数据安全性,之前安卓后端接口一直是用的HTTP,那么我想了想,HTTPS应该是入门级的了,赶紧找资料整理了下!

对于向权威机构申请过证书的网络地址,用OkHttp或者HttpsURLConnection都可以直接访问,不需要做额外的事情。但是申请证书要$$的,所以开发的时候我们接口经常是使用自签名证书,或者即使上线了也还是用自签名的,因为安卓用到的基本都是数据接口,又不会用浏览器访问,不付钱想不行咩!

访问自签名网址

使用keytool生成证书

keytool是JDK提供的管理加密密钥、X.509证书链和可信证书密钥库的简便工具。安卓开发必定安装了JDK并且一般都会配置好环境变量,所以你可以直接在终端或DOC窗口输入
keytool
命令来查看帮助。

1.生成密钥对

keytool -genkey -alias server -keyalg RSA -keystore server.jks


-alias
后面跟的是唯一别名,
-keystore
后面填保存秘钥对的文件路径

还可以添加一个
-validity 天数
声明有效期

需要注意的地方:执行命令之后第一个问题让你输入名字的地方最好设置成域名,比如这样
baidu.com
或者这样
localhost
,反正匹配你要调式的域名就对了,当然,如果你在安卓上调试,那么本地地址可能用不了。

2.导出证书

上面生成了服务端使用的密钥对,现在可以通过它生成证书给客户端使用

keytool -export -alias server -storepass 123456 -keystore server.jks -file server.cer


-storepass
后面跟的是你刚才设置的密码,不加这个也没关系,它会主动问你!;
-file
设置了保存证书的路径

服务端配置

这里我使用tomcat8进行测试,它的配置很简单,修改tomcat目录下的conf/server.xml文件,添加如下内容,这里设置了端口号为8443

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="密钥库文件路径,也就是.jks文件"
keystorePass="密码" />


安卓端配置

加载证书

把之前生成的证书(.cer)放到安卓项目的
assets
或者
raw
目录下,读取文件流用以下方法获取SSLSocketFactory 。

public static SSLSocketFactory getSslSocketFactory(InputStream certificates)
{
SSLContext sslContext = null;
try
{
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

Certificate ca;
try {
ca = certificateFactory.generateCertificate(certificates);

} finally {
certificates.close();
}

// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

} catch (Exception e)
{
e.printStackTrace();
}

return sslContext != null? sslContext.getSocketFactory():null;
}


OkHttp

在OkHttp中使用很简单,获取SSLSocketFactory之后通过OkHttp的构建方法传入就行了。

使用的OkHttp版本是
3.2.0


OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory)
.build();


设置完之后你就可以访问该证书对应的域名地址了,不需要别的附加操作了。

HttpsURLConnection

OkHttp的API与安卓中默认提供的URLConnection是很接近的,所以配置也是如出一辙。

URL url = new URL("https://....");

HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();

httpsURLConnection.setSSLSocketFactory(sslSocketFactory);


双向验证

双向认证需要两个密钥实体,一个放服务端一个放客户端。

前面我们已经实现单向的认证,现在只需要给客户端生成一个密钥库,并且让服务端信任客户端就可以了。

生成客户端密钥

keytool -genkey -alias android -keyalg RSA -keystore android.jks


导出客户端证书(字符串形式)

keytool -keystore android.jks -alias android -exportcert -rfc > android.pem


将导出的证书添加信任到服务端的密钥库

keytool -importcert -trustcacerts -alias android -keystore server.jks -file android.pem


服务端配置

修改tomcat目录下的conf/server.xml文件

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
sslProtocol="TLS"
keystoreFile="密钥库文件路径,也就是.jks文件"
keystorePass="密码"
//修改两条内容,其它和之前单向认证一样就行
clientAuth="true"
truststoreFile="和keystoreFile填一样" />


安卓端配置

刚才生成了客户端的密钥库
android.jks
。但是安卓默认是不支持jks格式的!比较常规的解决方式是用Portecle工具将它转换成bks文件。

点这里下载Portecle工具

下载完之后解压并在目录下运行命令:
java -jar portecle.jar
或者也可以直接双击它打开

运行之后就会出来UI界面,用它打开
android.jks
然后选菜单 Tools –> Change Keystore Type –> BKS 在弹出框输入密码进行转换,最后别忘记选菜单 File –> Save Keystore As 将它另存为
android.kbs
(名字随意)

可能会出现这样的异常:

java.security.KeyStoreException: java.io.IOException: Error initialising store of key store

解决办法是下载JCE然后替换掉
JDK\jre\lib\security
JRE\lib\security
这两个目录下的同名文件,并重启Portecle

生成kbs文件之后,把它放到安卓的目录下
assets
或者
raw


然后把获取SSLSocketFactory的方法改成下面这样

public static SSLSocketFactory getSslSocketFactory
(InputStream certificates,InputStream key,String keyPassword)
{
SSLContext sslContext = null;
try
{
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

Certificate ca;
try {
ca = certificateFactory.generateCertificate(certificates);

} finally {
certificates.close();
}

String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

String keyStoreType2 = "BKS";
KeyStore keyStore2 = KeyStore.getInstance(keyStoreType2);
keyStore2.load(key, keyPassword.toCharArray());

String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
kmf.init(keyStore2,keyPassword.toCharArray());

sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

} catch (Exception e)
{
e.printStackTrace();
}

return sslContext != null? sslContext.getSocketFactory():null;
}


上面的都改完之后,双向验证的配置就完成了。你可以打开浏览器访问下你配置好的地址,应该不能访问,提示你:
不接受您的登录证书,或者您的登录证书可能已过期
。因为你的系统没有加入刚才生成的客户端密钥库,安卓端像上面一样设置完SSLSocketFactory就可以正常访问了。

额外的,我把获取SSLSocketFactory的方法封装了下HttpsUtil,用
getSslSocketFactory
方法就行了

参考资料

Android HTTPS&SSL

http://developer.android.com/training/articles/security-ssl.html

Tomcat配置Https

http://crunchify.com/step-by-step-guide-to-enable-https-or-ssl-correct-way-on-apache-tomcat-server-port-8443/

keyTool官方使用说明

http://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html

可能出现的主机名验证失败的解决方案

http://stackoverflow.com/questions/33539929/how-to-solve-javax-net-ssl-sslpeerunverifiedexception-hostname-com-not-verifie

关于Android不支持.jks的问题

http://stackoverflow.com/questions/9312193/does-android-support-jks-keystore-type

.jks
.bks
可能产生的错误解决:

http://stackoverflow.com/questions/6363801/invalidkeyexception-illegal-key-size-java-code-throwing-exception-for-encryp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: