前言
最近有一个跟Https相关的问题需要解决,因此花时间学习了一下Android平台Https的使用,同时也看了一些Https的原理,这里分享一下学习心得。
HTTPS原理
HTTPS(Hyper Text Transfer Protocol Secure),是一种基于SSL/TLS的HTTP,所有的HTTP数据都是在SSL/TLS协议封装之上进行传输的。HTTPS协议是在HTTP协议的基础上,添加了SSL/TLS握手以及数据加密传输,也属于应用层协议。所以,研究HTTPS协议原理,最终其实就是研究SSL/TLS协议。
SSL/TLS协议作用
不使用SSL/TLS的HTTP通信,就是不加密的通信,所有的信息明文传播,带来了三大风险:
窃听风险:第三方可以获知通信内容。
篡改风险:第三方可以修改通知内容。
冒充风险:第三方可以冒充他人身份参与通信。
SSL/TLS协议是为了解决这三大风险而设计的,希望达到:
所有信息都是加密传输,第三方无法窃听。
具有校验机制,一旦被篡改,通信双方都会立刻发现。
配备身份证书,防止身份被冒充。
基本的运行过程
SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。但是这里需要了解两个问题的解决方案。
- 如何保证公钥不被篡改?
解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
- 公钥加密计算量太大,如何减少耗用的时间?
解决方法:每一次对话(session),客户端和服务器端都生成一个“对话密钥”(session key),用它来加密信息。由于“对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密“对话密钥”本身,这样就减少了加密运算的消耗时间。
因此,SSL/TLS协议的基本过程是这样的:
客户端向服务器端索要并验证公钥。
双方协商生成“对话密钥”。
双方采用“对话密钥”进行加密通信。
上面过程的前两布,又称为“握手阶段”。
握手阶段的详细过程

“握手阶段”涉及四次通信,需要注意的是,“握手阶段”的所有通信都是明文的。
客户端发出请求(ClientHello)
首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。在这一步中,客户端主要向服务器提供以下信息:
支持的协议版本,比如TLS 1.0版
一个客户端生成的随机数,稍后用于生成“对话密钥”。
支持的加密方法,比如RSA公钥加密。
支持的压缩方法。
这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应用向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。
服务器回应(ServerHello)
服务器收到客户端请求后,向客户端发出回应,这叫做ServerHello。服务器的回应包含以下内容:
确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
一个服务器生成的随机数,稍后用于生成“对话密钥”。
确认使用的加密方法,比如RSA公钥加密。
服务器证书。
除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供“客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
客户端回应
客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁发,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项消息。
一个随机数。该随机数用服务器公钥加密,防止被窃听。
编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
客户端握手结束通知,表示客户端的握手阶段已经结束。这一项通常也是前面发送的所有内容的hash值,用来供服务器校验。
上面第一项随机数,是整个握手阶段出现的第三个随机数,又称“pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把“会话密钥”。
服务器的最后回应
服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的“会话密钥”。然后,向客户端最后发送下面信息。
编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发生的所有内容的hash值,用来供客户端校验。
握手结束
至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用“会话密钥”加密内容。
服务器基于Nginx搭建HTTPS虚拟站点
之前一篇文章详细介绍了在服务器端如何生成SSL证书,并基于Nginx搭建HTTPS服务器,链接:Nginx搭建HTTPS服务器
Android实现HTTPS通信
由于各种原因吧,这里使用HttpClicent类讲解一下Android如何建立HTTPS连接。代码demo如下。
MainActivity.java
import java.io.BufferedReader; import java.io.InputStreamReader;
import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest;
import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.os.AsyncTask.Status; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView;
public class MainActivity extends Activity { private Button httpsButton; private TextView conTextView;
private CreateHttpsConnTask httpsTask;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
httpsButton = (Button) findViewById(R.id.create_https_button); httpsButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) { runHttpsConnection(); } });
conTextView = (TextView) findViewById(R.id.content_textview); conTextView.setText(“初始为空”); }
private void runHttpsConnection() { if (httpsTask == null || httpsTask.getStatus() == Status.FINISHED) { httpsTask = new CreateHttpsConnTask(); httpsTask.execute(); } }
private class CreateHttpsConnTask extends AsyncTask<Void, Void, Void> { private static final String HTTPS_EXAMPLE_URL = “自定义”; private StringBuffer sBuffer = new StringBuffer();
@Override protected Void doInBackground(Void… params) { HttpUriRequest request = new HttpPost(HTTPS_EXAMPLE_URL); HttpClient httpClient = HttpUtils.getHttpsClient(); try { HttpResponse httpResponse = httpClient.execute(request); if (httpResponse != null) { StatusLine statusLine = httpResponse.getStatusLine(); if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK) { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader( httpResponse.getEntity().getContent(), “UTF-8”)); String line = null; while ((line = reader.readLine()) != null) { sBuffer.append(line); }
} catch (Exception e) { Log.e(“https”, e.getMessage()); } finally { if (reader != null) { reader.close(); reader = null; } } } }
} catch (Exception e) { Log.e(“https”, e.getMessage()); } finally {
}
return null; }
@Override protected void onPostExecute(Void result) { if (!TextUtils.isEmpty(sBuffer.toString())) { conTextView.setText(sBuffer.toString()); } }
} }
HttpUtils.java
</div>
<div>
package com.example.photocrop;
import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP;
public class HttpUtils { public static HttpClient getHttpsClient() { BasicHttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET); HttpProtocolParams.setUseExpectContinue(params, true);
SchemeRegistry schReg = new SchemeRegistry(); schReg.register(new Scheme(“http”, PlainSocketFactory.getSocketFactory(), 80)); schReg.register(new Scheme(“https”, SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
return new DefaultHttpClient(connMgr, params); } }
activity_main.xml
</div>
<div>
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” android:orientation=“vertical”>
<Button android:id=”@+id/create_https_button” android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text="@string/hello_world" android:textSize=“16sp” />
<TextView android:id="@+id/content_textview" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:gravity=“center” android:textSize=“16sp” />
</LinearLayout>
Android使用DefaultHttpClient建立HTTPS连接,关键需要加入对HTTPS的支持:
<div>
```
schReg.<span class="keyword" style="font-weight: bold;">register</span>(<span class="keyword" style="font-weight: bold;">new</span> Scheme(<span class="string" style="color: #dd1144;">"https"</span>, SSLSocketFactory.getSocketFactory(), <span class="number" style="color: #009999;">443</span>));
加入对HTTPS的支持,就可以有效的建立HTTPS连接了,例如“https://www.google.com.hk”了,但是访问自己基于Nginx搭建的HTTPS服务器却不行,因为它使用了不被系统承认的自定义证书,会报出如下问题:No peer certificate。
使用自定义证书并忽略验证的HTTPS连接方式
解决证书不被系统承认的方法,就是跳过系统校验。要跳过系统校验,就不能再使用系统标准的SSL SocketFactory了,需要自定义一个。然后为了在这个自定义SSL SocketFactory里跳过校验,还需要自定义一个TrustManager,在其中忽略所有校验,即TrustAll。
MySSLSocketFactory.java实现代码如下:
import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; 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.X509Certificate;
import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance(“TLS”);
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() {
@Override public X509Certificate[] getAcceptedIssuers() { return null; }
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
} };
sslContext.init(null, new TrustManager[] { tm }, null); }
@Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); }
@Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); }
public static SSLSocketFactory getSocketFactory() { try { KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType()); trustStore.load(null, null); SSLSocketFactory factory = new MySSLSocketFactory(trustStore); return factory; } catch (Exception e) { e.getMessage(); return null; } } }
同时,需要修改DefaultHttpClient的register方法,改为自己构建的sslsocket:
</div>
<div>
```
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> HttpClient getCustomClient() {
BasicHttpParams <span class="keyword" style="font-weight: bold;">params</span> = <span class="keyword" style="font-weight: bold;">new</span> BasicHttpParams();
HttpProtocolParams.setVersion(<span class="keyword" style="font-weight: bold;">params</span>, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(<span class="keyword" style="font-weight: bold;">params</span>, HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(<span class="keyword" style="font-weight: bold;">params</span>, <span class="keyword" style="font-weight: bold;">true</span>);
SchemeRegistry schReg = <span class="keyword" style="font-weight: bold;">new</span> SchemeRegistry();
schReg.register(<span class="keyword" style="font-weight: bold;">new</span> Scheme(<span class="string" style="color: #dd1144;">"http"</span>, PlainSocketFactory.getSocketFactory(), <span class="number" style="color: #009999;">80</span>));
schReg.register(<span class="keyword" style="font-weight: bold;">new</span> Scheme(<span class="string" style="color: #dd1144;">"https"</span>, MySSLSocketFactory.getSocketFactory(), <span class="number" style="color: #009999;">443</span>));
ClientConnectionManager connMgr = <span class="keyword" style="font-weight: bold;">new</span> ThreadSafeClientConnManager(<span class="keyword" style="font-weight: bold;">params</span>, schReg);
<span class="keyword" style="font-weight: bold;">return</span> <span class="keyword" style="font-weight: bold;">new</span> DefaultHttpClient(connMgr, <span class="keyword" style="font-weight: bold;">params</span>);
}
这样就可以成功的访问自己构建的基于Nginx的HTTPS虚拟站点了。
缺陷:
不过,虽然这个方案使用了HTTPS,客户端和服务器端的通信内容得到了加密,嗅探程序无法得到传输的内容,但是无法抵挡“中间人攻击”。例如,在内网配置一个DNS,把目标服务器域名解析到本地的一个地址,然后在这个地址上使用一个中间服务器作为代理,它使用一个假的证书与客户端通讯,然后再由这个代理服务器作为客户端连接到实际的服务器,用真的证书与服务器通讯。这样所有的通讯内容都会经过这个代理,而客户端不会感知,这是由于客户端不校验服务器公钥证书导致的。
### 使用自定义证书建立HTTPS连接
为了防止上面方案可能导致的“中间人攻击”,我们可以下载服务器端公钥证书,然后将公钥证书编译到Android应用中,由应用自己来验证证书。
生成KeyStore
要验证自定义证书,首先要把证书编译到应用中,这需要使用keytool工具生产KeyStore文件。这里的证书就是指目标服务器的公钥,可以从web服务器配置的.crt文件或.pem文件获得。同时,你需要配置bouncycastle,我下载的是bcprov-jdk16-145.jar,至于配置大家自行google就好了。
keytool -importcert -v -trustcacerts -<span class="keyword" style="font-weight: bold;">alias</span> example -file www.example.com.crt -keystore example.bks -storetype <span class="constant">BKS</span> -providerclass org.bouncycastle.jce.provider.<span class="constant">BouncyCastleProvider</span> -providerpath /home/wzy/<span class="constant">Downloads</span>/java/jdk1.<span class="number" style="color: #009999;">7.0_60</span>/jre/lib/ext/bcprov-jdk16-<span class="number" style="color: #009999;">145</span>.jar -storepass pw123456
运行后将显示证书内容并提示你是否确认,输入Y回车即可。
生产KeyStore文件成功后,将其放在app应用的res/raw目录下即可。
使用自定义KeyStore实现连接
思路和TrushAll差不多,也是需要一个自定义的SSLSokcetFactory,不过因为还需要验证证书,因此不需要自定义TrustManager了。
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 org.apache.http.conn.ssl.SSLSocketFactory;
import android.content.Context;
public class CustomerSocketFactory extends SSLSocketFactory {
private static final String PASSWD = “pw123456”;
public CustomerSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); }
public static SSLSocketFactory getSocketFactory(Context context) { InputStream input = null; try { input = context.getResources().openRawResource(R.raw.example); KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType());
trustStore.load(input, PASSWD.toCharArray());
SSLSocketFactory factory = new CustomerSocketFactory(trustStore);
return factory; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } input = null; } } }
}
<div>
同时,需要修改DefaultHttpClient的register方法,改为自己构建的sslsocket:
</div>
<div>
```
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> HttpClient getSpecialKeyStoreClient(Context context) {
BasicHttpParams <span class="keyword" style="font-weight: bold;">params</span> = <span class="keyword" style="font-weight: bold;">new</span> BasicHttpParams();
HttpProtocolParams.setVersion(<span class="keyword" style="font-weight: bold;">params</span>, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(<span class="keyword" style="font-weight: bold;">params</span>, HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(<span class="keyword" style="font-weight: bold;">params</span>, <span class="keyword" style="font-weight: bold;">true</span>);
SchemeRegistry schReg = <span class="keyword" style="font-weight: bold;">new</span> SchemeRegistry();
schReg.register(<span class="keyword" style="font-weight: bold;">new</span> Scheme(<span class="string" style="color: #dd1144;">"http"</span>, PlainSocketFactory.getSocketFactory(), <span class="number" style="color: #009999;">80</span>));
schReg.register(<span class="keyword" style="font-weight: bold;">new</span> Scheme(<span class="string" style="color: #dd1144;">"https"</span>, CustomerSocketFactory.getSocketFactory(context), <span class="number" style="color: #009999;">443</span>));
ClientConnectionManager connMgr = <span class="keyword" style="font-weight: bold;">new</span> ThreadSafeClientConnManager(<span class="keyword" style="font-weight: bold;">params</span>, schReg);
<span class="keyword" style="font-weight: bold;">return</span> <span class="keyword" style="font-weight: bold;">new</span> DefaultHttpClient(connMgr, <span class="keyword" style="font-weight: bold;">params</span>);
}
</div>
转自:http://www.tuicool.com/articles/6NvEZj
转自[http://my.oschina.net/blackylin/blog/144136](http://my.oschina.net/blackylin/blog/144136)
参考[http://developer.android.com/training/articles/security-ssl.html#Concepts](http://developer.android.com/training/articles/security-ssl.html#Concepts)
[http://www.ibm.com/developerworks/cn/java/j-lo-ssltls/](http://www.ibm.com/developerworks/cn/java/j-lo-ssltls/)
# <a style="color: #336699;" name="t0"></a>问题:
项目中Android https或http请求地址重定向为HTTPS的地址,相信很多人都遇到了这个异常(无终端认证):
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
# <a style="color: #336699;" name="t1"></a>解决过程:
## <a style="color: #336699;" name="t2"></a>1.没遇到过的问题,搜索吧,少年
log里出现这个异常,作者第一次遇到,不知道啥意思。看下字面意思,是ssl协议中没有终端认证。SSL?[](http://my.oschina.net/js/ke/plugins/emoticons/images/14.gif)作者没用到ssl协议呀,只是通过httpClient请求一个重定向https的地址。
好吧,google下,知道了个差不多情况的帖子,http://www.eoeandroid.com/thread-161747-1-1.html。恩恩,一个不错的帖子,给出了个解决方案。照着来试下。添加个继承SSLSocketFactory的 自定义类。并在初始化httpclient支持https时,注册进去。看下面代码:
01 public class HttpClientHelper {
02 03 private static HttpClient httpClient; 04 05 private HttpClientHelper() { 06 } 07 08 public static synchronized HttpClient getHttpClient() { 09 10 if (null == httpClient) { 11 // 初始化工作 12 try { 13 KeyStore trustStore = KeyStore.getInstance(KeyStore 14 .getDefaultType()); 15 trustStore.load(null, null); 16 SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore); 17 sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //允许所有主机的验证 18 19 HttpParams params = new BasicHttpParams(); 20 21 HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 22 HttpProtocolParams.setContentCharset(params, 23 HTTP.DEFAULT_CONTENT_CHARSET); 24 HttpProtocolParams.setUseExpectContinue(params, true); 25 26 // 设置连接管理器的超时 27 ConnManagerParams.setTimeout(params, 10000); 28 // 设置连接超时 29 HttpConnectionParams.setConnectionTimeout(params, 10000); 30 // 设置socket超时 31 HttpConnectionParams.setSoTimeout(params, 10000); 32 33 // 设置http https支持 34 SchemeRegistry schReg = new SchemeRegistry(); 35 schReg.register(new Scheme(“http”, PlainSocketFactory 36 .getSocketFactory(), 80)); 37 schReg.register(new Scheme(“https”, sf, 443)); 38 39 ClientConnectionManager conManager = new ThreadSafeClientConnManager( 40 params, schReg); 41 42 httpClient = new DefaultHttpClient(conManager, params); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 return new DefaultHttpClient(); 46 } 47 } 48 return httpClient; 49 } 50 51 } 52 53 class SSLSocketFactoryEx extends SSLSocketFactory { 54 55 SSLContext sslContext = SSLContext.getInstance(“TLS”); 56 57 public SSLSocketFactoryEx(KeyStore truststore) 58 throws NoSuchAlgorithmException, KeyManagementException, 59 KeyStoreException, UnrecoverableKeyException { 60 super(truststore); 61 62 TrustManager tm = new X509TrustManager() { 63 64 @Override 65 public java.security.cert.X509Certificate[] getAcceptedIssuers() { 66 return null; 67 } 68 69 @Override 70 public void checkClientTrusted( 71 java.security.cert.X509Certificate[] chain, String authType) 72 throws java.security.cert.CertificateException { 73 74 } 75 76 @Override 77 public void checkServerTrusted( 78 java.security.cert.X509Certificate[] chain, String authType) 79 throws java.security.cert.CertificateException { 80 81 } 82 }; 83 84 sslContext.init(null, new TrustManager[] { tm }, null); 85 } 86 87 @Override 88 public Socket createSocket(Socket socket, String host, int port, 89 boolean autoClose) throws IOException, UnknownHostException { 90 return sslContext.getSocketFactory().createSocket(socket, host, port, 91 autoClose); 92 } 93 94 @Override 95 public Socket createSocket() throws IOException { 96 return sslContext.getSocketFactory().createSocket(); 97 } 98 }
<div id="highlighter_796606" class="syntaxhighlighter ">
<div class="lines">
<div class="line alt1">
</div>
</div>
</div>
ok,run下,狂乱的点到测试按钮,深吸口气,盯着eclipse中的logat。咦?神奇的竟然没有报之前的<span style="font-weight: 600;"> javax.net.ssl.SSLPeerUnverifiedException: No peer certificate</span>的异常了。服务端的数据正常返回了。[](http://my.oschina.net/js/ke/plugins/emoticons/images/13.gif),狂喜中…
## <a style="color: #336699;" name="t3"></a>2.了解并分析问题
狂喜中,得分析这问题诶。不然老大来问,啥情况?楞半天不知道咋说([](http://my.oschina.net/js/ke/plugins/emoticons/images/1.gif)作者就经常这样,所以吸取教训。所以的弄懂出现的问题,学习+汇报工作)。
思来想去,就是作者请求的是一个重定向https的地址。好吧,那就学习下https(之前被老大深深的教过,http就是request/response)。继续搜索吧,少年。下面总结下学习到的https知识。
### <a style="color: #336699;" name="t4"></a>2.1 https
HTTPS:超文本安全传输协议,和HTTP相比,多了一个SSL/TSL的认证过程,端口为443。(鄙视下之前说的)
**
作者没用到ssl协议呀,只是通过httpClient请求一个重定向https的地址
</blockquote>
<span style="font-weight: 600;">1.peer终端发送一个request,https服务端把支持的加密算法等以证书的形式返回一个身份信息(包含ca颁发机构和加密公钥等)。</span>
<span style="font-weight: 600;">2.获取证书之后,验证证书合法性。</span>
<span style="font-weight: 600;">3.随机产生一个密钥,并以证书当中的公钥加密。</span>
<span style="font-weight: 600;">4.request https服务端,把用公钥加密过的密钥传送给https服务端。</span>
<span style="font-weight: 600;">5.https服务端用自己的密钥解密,获取随机值。</span>
<span style="font-weight: 600;">6.之后双方传送数据都用此密钥加密后通信。</span>
```
看下面一张网上的得来的https的时序图:
[](http://static.oschina.net/uploads/space/2013/0711/214828_8G6b_587911.png)
### <a style="color: #336699;" name="t5"></a>2.2分析下出现问题的原因
好吧,大概的流程知道了。定位已经非常清楚了。在第2步验证证书时,无法验证。为啥无法验证呢?没有添加信任。详细参考下
[http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html](http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html)讲的非常清楚https-ssl的认证过程,膜拜下该作者[](http://my.oschina.net/js/ke/plugins/emoticons/images/13.gif)
这样想来,上面提供的解决方案就是添加默认信任全部证书。以此来通过接下来的通信。
## <a style="color: #336699;" name="t6"></a>3.解决问题
但是,这样问题是解决了。但是觉得还是不带靠谱(信任全部证书[](http://my.oschina.net/js/ke/plugins/emoticons/images/3.gif)有点危险)。继续噼噼啪啪的网上搜索一番。又找到了一种解决方案,其过程大致这样的:
<span style="font-weight: 600;">1.浏览器访问https地址,保存提示的证书到本地,放到android项目中的assets目录。</span>
<span style="font-weight: 600;">2.导入证书,代码如下。</span>
<span style="font-weight: 600;">3.把证书添加为信任。</span>
01 String requestHTTPSPage(String mUrl) {
02 InputStream ins = null;
03 String result = “”;
04 try {
05 ins = context.getAssets().open(“app_pay.cer”); //下载的证书放到项目中的assets目录中
06 CertificateFactory cerFactory = CertificateFactory
07 .getInstance(“X.509”);
08 Certificate cer = cerFactory.generateCertificate(ins);
09 KeyStore keyStore = KeyStore.getInstance(“PKCS12”, “BC”);
10 keyStore.load(null, null);
11 keyStore.setCertificateEntry(“trust”, cer);
12
13 SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore);
14 Scheme sch = new Scheme(“https”, socketFactory, 443);
15 HttpClient mHttpClient = new DefaultHttpClient();
16 mHttpClient.getConnectionManager().getSchemeRegistry()
17 .register(sch);
18
19 BufferedReader reader = null;
20 try {
21 Log.d(TAG, “executeGet is in,murl:” + mUrl);
22 HttpGet request = new HttpGet();
23 request.setURI(new URI(mUrl));
24 HttpResponse response = mHttpClient.execute(request);
25 if (response.getStatusLine().getStatusCode() != 200) {
26 request.abort();
27 return result;
28 }
29
30 reader = new BufferedReader(new InputStreamReader(response
31 .getEntity().getContent()));
32 StringBuffer buffer = new StringBuffer();
33 String line = null;
34 while ((line = reader.readLine()) != null) {
35 buffer.append(line);
36 }
37 result = buffer.toString();
38 Log.d(TAG, “mUrl=” + mUrl + “\nresult = ” + result);
39 } catch (Exception e) {
40 e.printStackTrace();
41 } finally {
42 if (reader != null) {
43 reader.close();
44 }
45 }
46 } catch (Exception e) {
47 // TODO: handle exception
48 } finally {
49 try {
50 if (ins != null)
51 ins.close();
52 } catch (IOException e) {
53 e.printStackTrace();
54 }
55 }
56 return result;
57 }
接着,验证下呗。吼吼,稀里糊涂的又可以了。感动的泪流满面。
# <a style="color: #336699;" name="t7"></a>最后总结:
2种方法都解决了作者遇到的问题,这里记录下。以防下次遇到,希望能给遇到相同问题朋友有所参考帮助。
转自:http://blog.csdn.net/lihenair/article/details/17441169
# <strong>Android 实现 HttpClient 请求Https**
如题,默认下,HttpClient是不能请求Https的,需要自己获取
<div class="dp-highlighter bg_java" style="color: #000000;">
<div class="bar">
<div class="tools" style="color: silver;">
**[java]** [view plain](http://blog.csdn.net/heynine/article/details/8279304#)[copy](http://blog.csdn.net/heynine/article/details/8279304#)
</div>
</div>
</div>
</div>
</div>
- <span style="color: black;"><span class="keyword" style="font-weight: bold; color: #006699;">private</span> <span class="keyword" style="font-weight: bold; color: #006699;">static</span> <span class="keyword" style="font-weight: bold; color: #006699;">final</span> <span class="keyword" style="font-weight: bold; color: #006699;">int</span> SET_CONNECTION_TIMEOUT = <span class="number" style="color: #c00000;">5</span> * <span class="number" style="color: #c00000;">1000</span>; </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">private</span> <span class="keyword" style="font-weight: bold; color: #006699;">static</span> <span class="keyword" style="font-weight: bold; color: #006699;">final</span> <span class="keyword" style="font-weight: bold; color: #006699;">int</span> SET_SOCKET_TIMEOUT = <span class="number" style="color: #c00000;">20</span> * <span class="number" style="color: #c00000;">1000</span>; </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> <span class="keyword" style="font-weight: bold; color: #006699;">static</span> HttpClient getNewHttpClient() { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">try</span> { </span>
- <span style="color: black;"> KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); </span>
- <span style="color: black;"> trustStore.load(<span class="keyword" style="font-weight: bold; color: #006699;">null</span>, <span class="keyword" style="font-weight: bold; color: #006699;">null</span>); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> SSLSocketFactory sf = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> MySSLSocketFactory(trustStore); </span>
- <span style="color: black;"> sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> HttpParams params = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> BasicHttpParams(); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> HttpConnectionParams.setConnectionTimeout(params, <span class="number" style="color: #c00000;">10000</span>); </span>
- <span style="color: black;"> HttpConnectionParams.setSoTimeout(params, <span class="number" style="color: #c00000;">10000</span>); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); </span>
- <span style="color: black;"> HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> SchemeRegistry registry = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> SchemeRegistry(); </span>
- <span style="color: black;"> registry.register(<span class="keyword" style="font-weight: bold; color: #006699;">new</span> Scheme(<span class="string" style="color: blue;">“http”</span>, PlainSocketFactory.getSocketFactory(), <span class="number" style="color: #c00000;">80</span>)); </span>
- <span style="color: black;"> registry.register(<span class="keyword" style="font-weight: bold; color: #006699;">new</span> Scheme(<span class="string" style="color: blue;">“https”</span>, sf, <span class="number" style="color: #c00000;">443</span>)); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> ClientConnectionManager ccm = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> ThreadSafeClientConnManager(params, registry); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> HttpConnectionParams.setConnectionTimeout(params, SET_CONNECTION_TIMEOUT); </span>
- <span style="color: black;"> HttpConnectionParams.setSoTimeout(params, SET_SOCKET_TIMEOUT); </span>
- <span style="color: black;"> HttpClient client = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> DefaultHttpClient(ccm, params); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">return</span> client; </span>
- <span style="color: black;"> } <span class="keyword" style="font-weight: bold; color: #006699;">catch</span> (Exception e) { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">return</span> <span class="keyword" style="font-weight: bold; color: #006699;">new</span> DefaultHttpClient(); </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> } </span>
** <span style="color: #000000;">下面是MySSLSocketFactory类</span>
<div class="dp-highlighter bg_java" style="color: #000000;">
<div class="bar">
<div class="tools" style="color: silver;">
<b>[java]** [view plain](http://blog.csdn.net/heynine/article/details/8279304#)[copy](http://blog.csdn.net/heynine/article/details/8279304#)
</div>
</div>
</div>
<div>
</div>
- <span style="color: black;"><span class="keyword" style="font-weight: bold; color: #006699;">private</span> <span class="keyword" style="font-weight: bold; color: #006699;">static</span> <span class="keyword" style="font-weight: bold; color: #006699;">class</span> MySSLSocketFactory <span class="keyword" style="font-weight: bold; color: #006699;">extends</span> SSLSocketFactory { </span>
- <span style="color: black;"> SSLContext sslContext = SSLContext.getInstance(<span class="string" style="color: blue;">“TLS”</span>); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> MySSLSocketFactory(KeyStore truststore) <span class="keyword" style="font-weight: bold; color: #006699;">throws</span> NoSuchAlgorithmException, </span>
- <span style="color: black;"> KeyManagementException, KeyStoreException, UnrecoverableKeyException { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">super</span>(truststore); </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> TrustManager tm = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> X509TrustManager() { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> <span class="keyword" style="font-weight: bold; color: #006699;">void</span> checkClientTrusted(X509Certificate[] chain, String authType) </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">throws</span> CertificateException { </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> <span class="keyword" style="font-weight: bold; color: #006699;">void</span> checkServerTrusted(X509Certificate[] chain, String authType) </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">throws</span> CertificateException { </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> X509Certificate[] getAcceptedIssuers() { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">return</span> <span class="keyword" style="font-weight: bold; color: #006699;">null</span>; </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> }; </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> sslContext.init(<span class="keyword" style="font-weight: bold; color: #006699;">null</span>, <span class="keyword" style="font-weight: bold; color: #006699;">new</span> TrustManager[] { tm }, <span class="keyword" style="font-weight: bold; color: #006699;">null</span>); </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> Socket createSocket(Socket socket, String host, <span class="keyword" style="font-weight: bold; color: #006699;">int</span> port, <span class="keyword" style="font-weight: bold; color: #006699;">boolean</span> autoClose) </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">throws</span> IOException, UnknownHostException { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">return</span> sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> </span>
- <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> Socket createSocket() <span class="keyword" style="font-weight: bold; color: #006699;">throws</span> IOException { </span>
- <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">return</span> sslContext.getSocketFactory().createSocket(); </span>
- <span style="color: black;"> } </span>
- <span style="color: black;"> } </span>
通过上面的方法获得HttpClient对象就可以请求Https了
转自:http://blog.csdn.net/heynine/article/details/8279304
💬 评论