https 不验证证书方式(信任所有证书)
前面写了http的联网方式,Android平台上经常有使用https的需求,对于https服务器使用的根证书是受信任的证书的话,实现https是非常简单的,直接用httpclient库就行了,与使用http几乎没有区别。但是在大多数情况下,服务器所使用的根证书是自签名的,或者签名机构不在设备的信任证书列表中,这样使用httpclient进行https连接就会失败。解决这个问题的办法有两种,一是在发起https连接之前将服务器证书加到httpclient的信任证书列表中,这个相对来说比较复杂一些,很容易出错;另一种办法是让httpclient信任所有的服务器证书,这种办法相对来说简单很多,但安全性则差一些,但在某些场合下有一定的应用场景。这一篇主要实现httpclient信任所有的服务器证书。
直接给出代码:
public class HttpsTestActivity extends Activity {
/** Called when the activity is first created. */
private TextView text;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.textView1);
GetHttps();
}
private void GetHttps() {
String https = "https://www.google.com.hk";
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new MyTrustManager() },
new SecureRandom());
HttpsURLConnection
.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection
.setDefaultHostnameVerifier(new MyHostnameVerifier());
HttpsURLConnection conn = (HttpsURLConnection) new URL(https)
.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null)
sb.append(line);
text.setText(sb.toString());
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getMessage());
}
}
private class MyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
// TODO Auto-generated method stub
return true;
}
}
private class MyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}
}
使用HttpsURLConnection时需要实现HostnameVerifier和X509TrustManager,这两个实现是必须的,要不会报安全验证异常。
使用httpclient对self-signedcertificate网站进行SSL连线
Android SDK在进行https连线时,对于自签署的凭证是会拒绝连线的,会得到Not trustedserver certificate的例外。如果使用HttpsURLConnection来连线,网络上可以找到一些破解方法,在此不多谈。使用apache httpclient其实执行效率比较差一点,但是他最大的好处就是有内建的机制储存cookie,并且也可以跟随server作自动转址。网络上资料比较多的是 httpclient 3.x版,Android使用httpclient 4(而且还有些实作被拿掉)唯一找到比较可信的来源是apachehttpclient官方的example。节录重点段落如下:
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File("my.keystore"));
try {
trustStore.load(instream, "nopassword".toCharArray());
} finally {
instream.close();
}
SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore);
Scheme sch = new Scheme("https", socketFactory, 443);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
本来以为是要制造一个假的空凭证骗过httpclient,从Android档案系统有点微妙开始改来改去,一连串不同的例外或直接crash就不多谈了。解决了档案路径,到底有没有建立等等方面的问题之后才终于发现,假凭证是不行的。
1、开启你PC或Mac上的浏览器连上目标网站,从凭证管理的地方把该网站的凭证汇出。每个浏览器做法都不同,请各位发挥正常工程师的水准做完这件事。以Firefox为例,找到site名后选汇出,得到一个文档名为your_site_name.crt的X509(PEM)凭证档。为了之后使用方便,先把这档案复制一份并把文档名改为your_site_name.pem。
2、将这个凭证格式从PEM转为BKS格式,这是关键性的一步啊。
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
这行中的getDefaultType会get到BKS。有J2ME经验的人会想说,我看多半是跟J2ME一样的Sun JKS格式吧。而且我不要用getDefaultType就好了,我可以自己指定为PEM、JKS格式啊。是的,你可以。只是你会得到错误讯息说KeyStore JKS implementation not found。测了半天,看起来Android的httpclient只吃BKS就对了,其他都没实作。
3、那格式要怎么转?根据网路上找到的资料,可以使用KeyToolIUI这个Java工具。打开后从介面选create→KeyStore,格式选BKS,自己命名一下,要不要设密码都可。接着选import→Keystore’sentry→Trusted certificate→Regular certificate。Source的部分选PEM并选到刚刚浏览器得到的那个档,Target当然选BKS和刚create出来的档。按OK之后会跳出Entries视窗,下方提示你enternew alias,随便取个名字后ok连打,顺利的话你应该就会有一个your_keystore.bks之类的档案了。
4、要把凭证档放在Androidapp能读到的地方,做法有两种,放在SDcard中,或直接放在resources中。
方案一:放在SD card的情形。首先模拟器要挂上SD card,这在Eclipse用AVD工具产生avd时就可以顺便指定SD了。如果习惯用指令的话,也可以用如下指令产生image并mount上:
$ mksdcard 512M my_sdcard.so
$ emulator -sdcard ./my_sdcard.so
再来要把凭证档copy到SDcard中,目前只知道指令的做法:
$ adb push file_path/your_keystore.bks /sdcard
如果有显示类似ftp传输速度之类的讯息应该就是成功了。
接着把android程式码中档案部分改一改:
FileInputStream instream = new FileInputStream(new File("/sdcard/your_keystore.bks"));
trustStore.load(instream, null);
如果没设密码,可以把keystoreload的第二个密码参数改成null,有设的话当然就改成你的密码。基本上这样应该就大功告成了。
方案二:再讲讲凭证放在resources的方法。
首先把档案复制到专案下的res/raw/your_keystore.bks。Eclipse没有错误的话就ok,否则多半是你档名不合规格,稍微改改。接着取用方法是把android程式码改成:
InputStream instream = getResources().openRawResource(R.raw.your_keystore);
其它同SD card。
5、还有一个地方要注意,就是SSLport。
Scheme sch = new Scheme("https", socketFactory, 443);
如果你要连的网站不是用port 443,这边请记得改掉。
6、另外如果要使用档案放在resources的做法,getResources()应该只能在Activityclass中执行,如果另外包装连线类别的话请不要直接服用上面的程式码,要自行从context抓到资源再传过去。