HTTPS全面解析

百度百科对https的介绍:
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。
HTTPS和HTTP的区别主要为以下四点:
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
握手过程
为了便于更好的认识和理解SSL 协议,这里着重介绍SSL 协议的握手协议。SSL 协议既用到了公钥加密技术又用到了对称加密技术,对称加密技术虽然比公钥加密技术的速度快,可是公钥加密技术提供了更好的身份认证技术。SSL 的握手协议非常有效的让客户和服务器之间完成相互之间的身份认证,其主要过程如下:
①客户端的浏览器向服务器传送客户端SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。
②服务器向客户端传送SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。
③客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。
④用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。
⑤如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。
⑥如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。
⑦服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于SSL 协议的安全数据通讯的加解密通讯。同时在SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。
⑧客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。
⑨服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。

⑩SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

下面我们就来写一下Https请求

String path = "https://www.baidu.com";
URL url = new URL(path);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
InputStream is = conn.getInputStream();
String string = Utils.inputStream2String(is);
System.out.println(string);
这个可以正常访问,它就会将百度的html返回回来,那下面我们就url改成其他的看看,将其改成我tomcat下的一个工程网址(已放开了https配置,设置了自签名证书):https://localhost:8443/Test/myTest

String path = "https://localhost:8443/Test/myTest";
URL url = new URL(path);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
InputStream is = conn.getInputStream();
String string = Utils.inputStream2String(is);
System.out.println(string);
这时就访问不了了,报了一个javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target错误
那么这是为什么了呢,百度的https就可以访问,而自己tomcat的https就不可以访问呢?咱再来看一看百度百科
信任主机的问题
采用https的服务器必须从CA (Certificate Authority)申请一个用于证明服务器用途类型的证书。该证书只有用于对应的服务器的时候,客户端才信任此主机。所以所有的银行系统网站,关键部分应用都是https 的。客户通过信任该证书,从而信任了该主机。其实这样做效率很低,但是银行更侧重安全。
原来只有从CA机构申请的证书默认情况是受信任的,客户端可以直接访问。而我自己tomcat下的证书是使用java下的keytool工具自动生成的,并不是CA机构颁发的,所以默认是不受信任的。那么我们如何处理,使其受信任并且可以访问呢?有两种方式:1.无条件的设置信任所有的证书。2.设置只信任与服务器设置的jks对应的cer证书

1.无条件的设置信任所有的证书,但是这样做是不安全的

public static void main(String[] args) {
	try {
	//初始化SSLContext
	SSLContext context = SSLContext.getInstance("TLS");//TLS和SSL都可以
	//设置信任管理器
	TrustManager[] managers = new TrustManager[]{new EmptyTrustManager()};
	context.init(null, managers, null);
	//这个必须在HttpsURLConnection生成对象之前设置
	HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
			
	String path = "https://localhost:8443/Test/myTest";
	URL url = new URL(path);
	HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
	//这时设置SSLSocketFactory没有用
	//conn.setDefaultSSLSocketFactory(conn.getSSLSocketFactory());
	InputStream is = conn.getInputStream();
	String string = Utils.inputStream2String(is);
	System.out.println(string);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

static class EmptyTrustManager implements X509TrustManager {

	@Override
	public void checkClientTrusted(X509Certificate[] chain, String authType)
			throws CertificateException {

	}

	@Override
	public void checkServerTrusted(X509Certificate[] chain, String authType)
			throws CertificateException {

	}

	@Override
	public X509Certificate[] getAcceptedIssuers() {
		return null;
	}

}
2.设置只信任与服务器设置的jks对应的cer证书。当你将自己的项目的网络请求改为https时,后台哥们会给你一个xxx.cer证书,这个证书就会和后台的xxx.jks进行验证校验。我们可以将xxx.cer放到工程中,去读取文件,也可以将其打印成字符串,将字符串放到代码中去读取。以下我是以字符串的形式使用的

static String chen = "-----BEGIN CERTIFICATE-----\n"+
			"MIIDWTCCAkGgAwIBAgIENIaROTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJDTjENMAsGA1UE"+
			"CBMEY2hlbjENMAsGA1UEBxMEY2hlbjENMAsGA1UEChMEY2hlbjENMAsGA1UECxMEY2hlbjESMBAG"+
			"A1UEAxMJbG9jYWxob3N0MB4XDTE3MDIxMzA5NDIzN1oXDTI2MTIyMzA5NDIzN1owXTELMAkGA1UE"+
			"BhMCQ04xDTALBgNVBAgTBGNoZW4xDTALBgNVBAcTBGNoZW4xDTALBgNVBAoTBGNoZW4xDTALBgNV"+
			"BAsTBGNoZW4xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC"+
			"ggEBAKolNf440rZx58iz5P58VrWWPMQDZPMYuDDeZMENHRIUOu+PmjKwpmJUqVnKMvyGFj9VtoPE"+
			"kZE7NQLzJ0FIfaTBLaHRTYLisk3qnVXgZyZq/rrLq935SURCRl76NP+pAyXBoWVUkvzRYwXfhFUi"+
			"zO5yjH+ZFfQzjGWCugd8oYCLKmP+pJnlP/faNu7YDjL9JkOm6KiEnmkklkSJXrBBxmpsy0lSShwY"+
			"rNoZ4wW8XwgbNdwjM+Z4+IeOD/gBZMSrI8n8TLKBFH6IydFJOkwhHO1BvdzNQHrcXF0CRqM5ctW4"+
			"K7LXXE8OJg59b28ghdiqVAWRnSEyIYNgEBLkXnaU95UCAwEAAaMhMB8wHQYDVR0OBBYEFDt5QPgy"+
			"T3yfkb9iXV92Wzo1YndfMA0GCSqGSIb3DQEBCwUAA4IBAQA9ZVQIGQERDvDlsjlSzmhcrX0Gpdxf"+
			"kFmgXEc+Tck04l2VaT70YXYztcWTm8iPlxAOTAjxRE0syIbbAQ4UPba0hFINCPpvCD31ax3ukFUj"+
			"NJH+rZUwo1HjV8qzKAA4nd3f31PRg3yyAzmgfAwqVNxiq26YVglvV6h3TnC6dgcnV4sejOvv5nTA"+
			"7qc0cbQvGIEz4bEwIc+0l2/gwIoeUIquM6OAQTVzX9LuJ1aUTpenut+9G+vkEO3pfy4gdnOOXntW"+
			"uzqhaUV9gxOd1et10n1pvDcYNG6hhAkvotgV8Pl+NnDZ22Hqin1XB1cOZv18zHxC9WThRma3VN0V"+
			"z08eItRz\n"+
			"-----END CERTIFICATE-----";

	public static void main(String[] args) {
		try {
			ByteArrayInputStream baos = new ByteArrayInputStream(chen.getBytes());
			SSLContext sslContext = getSSLContext(baos);
			HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
			
			String path = "https://localhost:8443/Test/myTest";
			URL url = new URL(path);
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			//conn.setDefaultSSLSocketFactory(conn.getSSLSocketFactory());
			InputStream is = conn.getInputStream();
			String string = Utils.inputStream2String(is);
			System.out.println(string);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	static SSLContext getSSLContext(InputStream is){
		try{
			//初始化SSLContext
			SSLContext context = SSLContext.getInstance("TLS");//TLS和SSL都可以
			// 1.导入证书
			Certificate certificate = CertificateFactory.getInstance("X.509")
					.generateCertificate(is);
			// 2.证书导入密钥库
			KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
			keyStore.load(null);
			keyStore.setCertificateEntry("srca", certificate);
			// 3.把密钥库放入信任管理器
			TrustManagerFactory trustManagerFactory = TrustManagerFactory
					.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			trustManagerFactory.init(keyStore);
			// 4.生成信任管理器
			TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
			// 将信任管理器设置到SSLContext
			context.init(null, trustManagers, null);
			
			return context;
		}catch(Exception e){
			e.printStackTrace();
		}
		return null;
	}
如果url中的Hostname用的是IP则情况就不一样了,如果是String path = "https://127.0.0.1:8443/Test/myTest";再次访问的时候,会报javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present错误。
所以在HttpsURLConnection中的默认主机名验证的方法中应返回true以表示通过,

HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
				
				@Override
				public boolean verify(String hostname, SSLSession session) {
					
					return true;
				}
			});
此设置应和SSLSocketFactory一样,在生成HttpsURLConnection之前设置,不然无效,所以main方法中代码应为

public static void main(String[] args) {
		try {
			ByteArrayInputStream baos = new ByteArrayInputStream(chen.getBytes());
			SSLContext sslContext = getSSLContext(baos);
			HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
			HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
				
				@Override
				public boolean verify(String hostname, SSLSession session) {
					
					return true;
				}
			});
			
			String path = "https://127.0.0.1:8443/Test/myTest";
			URL url = new URL(path);
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			//conn.setDefaultSSLSocketFactory(conn.getSSLSocketFactory());
			InputStream is = conn.getInputStream();
			String string = Utils.inputStream2String(is);
			System.out.println(string);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

这时就可以正常访问了。
服务器端配置https

下面我们就来讲解如何生成自签名证书,和tomcat如何部署,完成这两步tomcat就可以使用https访问了,首先我们使用java自带的keytool工具生成证书,win+R打开运行,输入cmd打开cmd命令窗体,按照如下方式输入:

如何生成https证书:

1.生成jks,使用如下的命令就会生成一个chen.jks,密码为123456,jks是服务端tomcat配置https时使用的证书

C:\Program Files\Java\jdk1.7.0_67\bin>keytool -genkey -alias chen -keyalg
 RSA -keystore C:\CJ\chen.jks -validity 3600 -storepass 123456
您的名字与姓氏是什么?
  [Unknown]:  localhost
您的组织单位名称是什么?
  [Unknown]:  chen
您的组织名称是什么?
  [Unknown]:  chen
您所在的城市或区域名称是什么?
  [Unknown]:  chen
您所在的省/市/自治区名称是什么?
  [Unknown]:  chen
该单位的双字母国家/地区代码是什么?
  [Unknown]:  CN
CN=localhost, OU=chen, O=chen, L=chen, ST=chen, C=CN是否正确?
  [否]:  y

输入  的密钥口令
        (如果和密钥库口令相同, 按回车):
再次输入新口令:
2.生成cer,使用如下命令和chen.jks就会生成一个chen.cer,xxx.cer是客户端https访问时用于与服务端jks进行校验的

C:\Program Files\Java\jdk1.7.0_67\bin>keytool -export -alias chen -file C
:\CJ\chen.cer -keystore C:\CJ\chen.jks -storepass 123456
存储在文件  中的证书
3.打印cer,使用如下命令就可以打印cer中的字符串,就是上述提到的将cer打印成字符串,放入代码中,就不需要将xxx.cer放到工程中了。
C:\Program Files\Java\jdk1.7.0_67\bin>keytool -printcert -rfc -file C:\CJ\chen.cer
-----BEGIN CERTIFICATE-----
MIIDWTCCAkGgAwIBAgIENIaROTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJDTjENMAsGA1UE
CBMEY2hlbjENMAsGA1UEBxMEY2hlbjENMAsGA1UEChMEY2hlbjENMAsGA1UECxMEY2hlbjESMBAG
A1UEAxMJbG9jYWxob3N0MB4XDTE3MDIxMzA5NDIzN1oXDTI2MTIyMzA5NDIzN1owXTELMAkGA1UE
BhMCQ04xDTALBgNVBAgTBGNoZW4xDTALBgNVBAcTBGNoZW4xDTALBgNVBAoTBGNoZW4xDTALBgNV
BAsTBGNoZW4xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKolNf440rZx58iz5P58VrWWPMQDZPMYuDDeZMENHRIUOu+PmjKwpmJUqVnKMvyGFj9VtoPE
kZE7NQLzJ0FIfaTBLaHRTYLisk3qnVXgZyZq/rrLq935SURCRl76NP+pAyXBoWVUkvzRYwXfhFUi
zO5yjH+ZFfQzjGWCugd8oYCLKmP+pJnlP/faNu7YDjL9JkOm6KiEnmkklkSJXrBBxmpsy0lSShwY
rNoZ4wW8XwgbNdwjM+Z4+IeOD/gBZMSrI8n8TLKBFH6IydFJOkwhHO1BvdzNQHrcXF0CRqM5ctW4
K7LXXE8OJg59b28ghdiqVAWRnSEyIYNgEBLkXnaU95UCAwEAAaMhMB8wHQYDVR0OBBYEFDt5QPgy
T3yfkb9iXV92Wzo1YndfMA0GCSqGSIb3DQEBCwUAA4IBAQA9ZVQIGQERDvDlsjlSzmhcrX0Gpdxf
kFmgXEc+Tck04l2VaT70YXYztcWTm8iPlxAOTAjxRE0syIbbAQ4UPba0hFINCPpvCD31ax3ukFUj
NJH+rZUwo1HjV8qzKAA4nd3f31PRg3yyAzmgfAwqVNxiq26YVglvV6h3TnC6dgcnV4sejOvv5nTA
7qc0cbQvGIEz4bEwIc+0l2/gwIoeUIquM6OAQTVzX9LuJ1aUTpenut+9G+vkEO3pfy4gdnOOXntW
uzqhaUV9gxOd1et10n1pvDcYNG6hhAkvotgV8Pl+NnDZ22Hqin1XB1cOZv18zHxC9WThRma3VN0V
z08eItRz
-----END CERTIFICATE-----
4.tomcat服务器配置 ,在tomcat下的conf文件夹下的server.xml中配置

 
在server.xml中的https默认配置的port为8443,你也可以修改,当你的电脑的8443端口被其他应用占用的时候,你就可以修改。这是重新启动tomcat时,这时你的服务器就可以使用https访问了。
这时该服务器用上面的网络请求就可以访问了。

双向证书验证
前面的https请求时,是服务器一个chen.jks,客户端一个chen.cer。那么双向证书验证,就还需要再次之前的配置上,再加上服务器一个chen_client.cer,客户端一个chen_client.jks。

重新生成一个证书
1.生成jks

C:\Program Files\Java\jdk1.7.0_67\bin>keytool -genkey -alias chen -keyalg
 RSA -keystore C:\CJ\chen.jks -validity 3600 -storepass 123456
您的名字与姓氏是什么?
  [Unknown]:  localhost
您的组织单位名称是什么?
  [Unknown]:  chen
您的组织名称是什么?
  [Unknown]:  chen
您所在的城市或区域名称是什么?
  [Unknown]:  chen
您所在的省/市/自治区名称是什么?
  [Unknown]:  chen
该单位的双字母国家/地区代码是什么?
  [Unknown]:  CN
CN=localhost, OU=chen, O=chen, L=chen, ST=chen, C=CN是否正确?
  [否]:  y

输入  的密钥口令
        (如果和密钥库口令相同, 按回车):
再次输入新口令:
2.生成cer

C:\Program Files\Java\jdk1.7.0_67\bin>keytool -export -alias chen -file C
:\CJ\chen.cer -keystore C:\CJ\chen.jks -storepass 123456
存储在文件  中的证书
需要将cer证书添加到jks中,不然,如果服务端使用cer证书,会报一个Invalid keystore format错误

C:\Program Files\Java\jdk1.7.0_67\bin>keytool -import -alias chen_client -file C
:\CJ\chen_client.cer -keystore C:\CJ\chen_client_for_server.jks
3.修改tomcat的配置文件

需要将之前的clientAuth="false"改为clientAuth="true",在增加一个字段truststoreFile="C:\\CJ\\chen_client_for_server.jks",指明jks的路径。

 
当是双向证书验证验证时,如果在tomcat/conf/sever.xml文件中的配置属性clientAuth="false",没有将其改为true,这是的双向证书验证验依然是单向的,客户端不做任何修改,https依然可以访问,如果将其改为true,就访问不了了。
这时重启服务器时,之前的https网络请求就请求不了了,报java.net.SocketException: Software caused connection abort: recv failed。这时我们需要修改一下网络请求代码,context.init(null, trustManagers, null);第一个参数是KeyManager[],是客户端的key,就是使用我们第二次生成的chen_client.jks,生成的KeyManager[]。添加如下代码:
//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream iStream = new FileInputStream("chen_client.jks");
clientKeyStore.load(iStream, "123456".toCharArray());
//将keystore放入密钥管理器
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
 //生成密钥管理器
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
// 将信任管理器设置到SSLContext
context.init(keyManagers, trustManagers, null);
这时就可以进行网络请求了(java平台),客户端和服务器就是双向证书验证方式,网络请求就会更加的安全可靠。(如果是双向证书验证的tomcat,如果客户端不给kayManagers就会报 javax.net.ssl.SSLException: Connection closed by peer 错误)
这时我们将代码移植到Android工程中,跑起来,访问网络失败,报java.io.IOException: Wrong version of key store.异常。这是因为Java平台默认识别jks格式的证书文件,但是android平台只识别bks格式的证书文件。所以这时我们就需要将jks格式转成bks格式,我们可以使用Portecle工具进行转换: 下载。下载完之后解压,里面有一个Portecle.jar,在DOS界面,使用jave -jar Portecle.jar打开图形界面,操作步骤:1.File->Open Keystore File->选择chen_client.jks->输入密码 2.Tools->Change Keystore Type->BKS->输入密码 3.Save Keystore->选择保存路径(命名:chen_client.bks)。然后将Android工程中的chen_client.jks替换为chen_client.bks。重新运行Android项目,就可以正常访问了。Android中的完整代码如下:

URLConnection双向证书验证的https请求:

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import com.tydz.taoyuan.R;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class HttpsActivity extends AppCompatActivity {

    static Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mTextView.setText(msg.obj.toString());
        }
    };

    static String chen = "-----BEGIN CERTIFICATE-----\n" +
            "MIIDWTCCAkGgAwIBAgIEcWnCMTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJDTjENMAsGA1UE\n" +
            "CBMEY2hlbjENMAsGA1UEBxMEY2hlbjENMAsGA1UEChMEY2hlbjENMAsGA1UECxMEY2hlbjESMBAG\n" +
            "A1UEAxMJbG9jYWxob3N0MB4XDTE3MDIxMzE0MTIzNVoXDTI2MTIyMzE0MTIzNVowXTELMAkGA1UE\n" +
            "BhMCQ04xDTALBgNVBAgTBGNoZW4xDTALBgNVBAcTBGNoZW4xDTALBgNVBAoTBGNoZW4xDTALBgNV\n" +
            "BAsTBGNoZW4xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" +
            "ggEBAOCj9+qaE/DcDtJnEpeSc0+byTO3/yM673C4sgax7n4lCnjtmhgqiwASTCOnEMRJ6llmo/M8\n" +
            "+msz9i+1vE4lte7AkZ/3Wfu04BXq9wmDlVNWU3kU6TfZMUhqifTvRC6zQmfJaBdS6SSfmBslrUUC\n" +
            "ntD19vvGRLPkEt05s0itG54ex41fSgA4pBtj6qpBjLMS/03fCD6y3YdL+r0dN6bDxAuLo+2flJEy\n" +
            "9CrnXEzLcZ172zD+5mfvhFI7F1fF1kdjT4pNi22sap5AOFZazaK7snq03cLTchC7yBwcPCJ6IqzY\n" +
            "Yw6bVb79cj6nntbTb6lsrL7wv2kR8ubtiRTrrtCP2YMCAwEAAaMhMB8wHQYDVR0OBBYEFF5ZFY02\n" +
            "FejgLSsO9DOZppwUhGS4MA0GCSqGSIb3DQEBCwUAA4IBAQCNg76X3tRCtnDi7cT5M8ADMzSUlYvA\n" +
            "CaVYKQ+lB3WBLuqR3wVVJ/zeSGqiR4F59cj6Oij+fk5Fu05n+0nXX/sdirf2RTW1eaA+SzPYXdgA\n" +
            "0u8XJdaPu58q0GCPFpUv24ljzQEr/hZBS9HWlHmkgByecpgrrToOc3jJNIQr8g1NVO/b8RCBFyx8\n" +
            "Ax+Ytp8XNGKbmMnXifBxBnhMCT0DptmZuYsTacZ5qLqEhszChYSM3drYhoNId1HlLLeQbUumbB/8\n" +
            "N1BVVH2LYPTJgKdXTQAzTWiUAss/HD78fe30qI3MDrZ1h4eF+vCHqOwAOiLxQ0Rpb5oMsVCblmoR\n" +
            "+ool1ljG\n" +
            "-----END CERTIFICATE-----";
    private static TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_https);

        mTextView = (TextView) findViewById(R.id.textview);

        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    net();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private void net() throws Exception{

        ByteArrayInputStream baos = new ByteArrayInputStream(chen.getBytes());
        //InputStream iStream = new FileInputStream("chen_client.jks");
        InputStream iStream = getAssets().open("chen_client.bks");
        SSLContext sslContext = getSSLContext(baos,iStream);
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String hostname, SSLSession session) {

                return true;
            }
        });

        String path = "https://10.0.2.2:8444/OkHttp/text";
        URL url = new URL(path);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        //********************************
        //POST请求
        /*
        conn.addRequestProperty("Content-Type", "application/json");
        conn.addRequestProperty("Connection", "Keep-Alive");// 维持长连接
        conn.addRequestProperty("Charset", "UTF-8");

        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        conn.setRequestMethod("POST");

        OutputStream outputStream = conn.getOutputStream();
        outputStream.write("{\"name\":chen}".getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
        */
        //************************************

        //conn.setDefaultSSLSocketFactory(conn.getSSLSocketFactory());
        InputStream is = conn.getInputStream();
        String string = inputStream2String(is);
        System.out.println(string);

        sendMsg(string);

    }

    private void sendMsg(String msg){
        Message message = mHandler.obtainMessage();
        message.obj = msg;
        mHandler.sendMessage(message);
    }

    SSLContext getSSLContext(InputStream cer,InputStream bks){
        try{
            //初始化SSLContext
            SSLContext context = SSLContext.getInstance("TLS");//TLS和SSL都可以
            // 1.导入证书
            Certificate certificate = CertificateFactory.getInstance("X.509")
                    .generateCertificate(cer);
            // 2.证书导入密钥库
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setCertificateEntry("srca", certificate);
            // 3.把密钥库放入信任管理器
            TrustManagerFactory trustManagerFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            // 4.生成信任管理器
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            //初始化keystore
            KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            clientKeyStore.load(bks, "123456".toCharArray());
            //将keystore放入密钥管理器
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
            //生成密钥管理器
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            // 将信任管理器设置到SSLContext
            context.init(keyManagers, trustManagers, null);

            return context;
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public static String inputStream2String(InputStream in) throws IOException {
        int len = -1;
        byte[] buffer = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while ((len = in.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.close();

        return baos.toString("UTF-8");
    }
}
Volley双向证书验证的https请求:
Volley因为API>=9底层的就是用HttpsURLConnection,HttpsURLConnection实现的,所以volley实现https的代码可以参考java的HttpsURLConnection的,volley的http请求时的请求队列:RequestQueue queue = Volley.newRequestQueue(getApplication());,newRequestQueue(Context)还有一个重载方法newRequestQueue(Context context, HttpStack stack),这个就是https的入口,HurlStack构造中就有一个参数传的就是SSLSocketFactory,所以volley的https的请求代码如下:

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;

public class MainActivity extends AppCompatActivity {

    static Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mTextView.setText(msg.obj.toString());
        }
    };


    static String chen = "-----BEGIN CERTIFICATE-----\n" +
            "MIIDWTCCAkGgAwIBAgIEcWnCMTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJDTjENMAsGA1UE\n" +
            "CBMEY2hlbjENMAsGA1UEBxMEY2hlbjENMAsGA1UEChMEY2hlbjENMAsGA1UECxMEY2hlbjESMBAG\n" +
            "A1UEAxMJbG9jYWxob3N0MB4XDTE3MDIxMzE0MTIzNVoXDTI2MTIyMzE0MTIzNVowXTELMAkGA1UE\n" +
            "BhMCQ04xDTALBgNVBAgTBGNoZW4xDTALBgNVBAcTBGNoZW4xDTALBgNVBAoTBGNoZW4xDTALBgNV\n" +
            "BAsTBGNoZW4xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" +
            "ggEBAOCj9+qaE/DcDtJnEpeSc0+byTO3/yM673C4sgax7n4lCnjtmhgqiwASTCOnEMRJ6llmo/M8\n" +
            "+msz9i+1vE4lte7AkZ/3Wfu04BXq9wmDlVNWU3kU6TfZMUhqifTvRC6zQmfJaBdS6SSfmBslrUUC\n" +
            "ntD19vvGRLPkEt05s0itG54ex41fSgA4pBtj6qpBjLMS/03fCD6y3YdL+r0dN6bDxAuLo+2flJEy\n" +
            "9CrnXEzLcZ172zD+5mfvhFI7F1fF1kdjT4pNi22sap5AOFZazaK7snq03cLTchC7yBwcPCJ6IqzY\n" +
            "Yw6bVb79cj6nntbTb6lsrL7wv2kR8ubtiRTrrtCP2YMCAwEAAaMhMB8wHQYDVR0OBBYEFF5ZFY02\n" +
            "FejgLSsO9DOZppwUhGS4MA0GCSqGSIb3DQEBCwUAA4IBAQCNg76X3tRCtnDi7cT5M8ADMzSUlYvA\n" +
            "CaVYKQ+lB3WBLuqR3wVVJ/zeSGqiR4F59cj6Oij+fk5Fu05n+0nXX/sdirf2RTW1eaA+SzPYXdgA\n" +
            "0u8XJdaPu58q0GCPFpUv24ljzQEr/hZBS9HWlHmkgByecpgrrToOc3jJNIQr8g1NVO/b8RCBFyx8\n" +
            "Ax+Ytp8XNGKbmMnXifBxBnhMCT0DptmZuYsTacZ5qLqEhszChYSM3drYhoNId1HlLLeQbUumbB/8\n" +
            "N1BVVH2LYPTJgKdXTQAzTWiUAss/HD78fe30qI3MDrZ1h4eF+vCHqOwAOiLxQ0Rpb5oMsVCblmoR\n" +
            "+ool1ljG\n" +
            "-----END CERTIFICATE-----";
    private static TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.textview);

    }

    public void click(View view){
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    volley();

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


    void volley() throws Exception{
        String path = "https://10.0.2.2:8444/OkHttp/text";
        ByteArrayInputStream baos = new ByteArrayInputStream(chen.getBytes());
        //InputStream iStream = new FileInputStream("chen_client.jks");
        InputStream iStream = getAssets().open("chen_client.bks");
        SSLContext sslContext = getSSLContext(baos,iStream);
        //SSLSocketFactory在HurlStack内部已经实现
        //HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String hostname, SSLSession session) {

                return true;
            }
        });
        HurlStack hurlStack = new HurlStack(null,sslContext.getSocketFactory());
        RequestQueue queue = Volley.newRequestQueue(getApplication(),hurlStack);
        //get
        StringRequest request = new StringRequest(path, new Response.Listener() {
            @Override
            public void onResponse(String response) {
                sendMsg(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
                sendMsg(error.toString());
            }
        });
        //post
	/*
	Map map = new HashMap<>();
        map.put("userName", "183********");
        map.put("password", "111111");
        JSONObject object = new JSONObject(map);

        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, path, object, new Response.Listener() {
            @Override
            public void onResponse(JSONObject response) {
                sendMsg(response.toString());
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
                sendMsg(error.toString());
            }
        });
	*/
        queue.add(request);
    }


    private void sendMsg(String msg){
        Message message = mHandler.obtainMessage();
        message.obj = msg;
        mHandler.sendMessage(message);
    }

    SSLContext getSSLContext(InputStream cer,InputStream bks){
        try{
            //初始化SSLContext
            SSLContext context = SSLContext.getInstance("TLS");//TLS和SSL都可以
            // 1.导入证书
            Certificate certificate = CertificateFactory.getInstance("X.509")
                    .generateCertificate(cer);
            // 2.证书导入密钥库
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setCertificateEntry("srca", certificate);
            // 3.把密钥库放入信任管理器
            TrustManagerFactory trustManagerFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            // 4.生成信任管理器
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            //初始化keystore
            KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            clientKeyStore.load(bks, "123456".toCharArray());
            //将keystore放入密钥管理器
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
            //生成密钥管理器
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            // 将信任管理器设置到SSLContext
            context.init(keyManagers, trustManagers, new SecureRandom());

            return context;
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

}

OkHttp双向证书验证的https请求:

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;

public class MainActivity extends AppCompatActivity {

    static Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mTextView.setText(msg.obj.toString());
        }
    };

    static String chen = "-----BEGIN CERTIFICATE-----\n" +
            "MIIDWTCCAkGgAwIBAgIEcWnCMTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJDTjENMAsGA1UE\n" +
            "CBMEY2hlbjENMAsGA1UEBxMEY2hlbjENMAsGA1UEChMEY2hlbjENMAsGA1UECxMEY2hlbjESMBAG\n" +
            "A1UEAxMJbG9jYWxob3N0MB4XDTE3MDIxMzE0MTIzNVoXDTI2MTIyMzE0MTIzNVowXTELMAkGA1UE\n" +
            "BhMCQ04xDTALBgNVBAgTBGNoZW4xDTALBgNVBAcTBGNoZW4xDTALBgNVBAoTBGNoZW4xDTALBgNV\n" +
            "BAsTBGNoZW4xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" +
            "ggEBAOCj9+qaE/DcDtJnEpeSc0+byTO3/yM673C4sgax7n4lCnjtmhgqiwASTCOnEMRJ6llmo/M8\n" +
            "+msz9i+1vE4lte7AkZ/3Wfu04BXq9wmDlVNWU3kU6TfZMUhqifTvRC6zQmfJaBdS6SSfmBslrUUC\n" +
            "ntD19vvGRLPkEt05s0itG54ex41fSgA4pBtj6qpBjLMS/03fCD6y3YdL+r0dN6bDxAuLo+2flJEy\n" +
            "9CrnXEzLcZ172zD+5mfvhFI7F1fF1kdjT4pNi22sap5AOFZazaK7snq03cLTchC7yBwcPCJ6IqzY\n" +
            "Yw6bVb79cj6nntbTb6lsrL7wv2kR8ubtiRTrrtCP2YMCAwEAAaMhMB8wHQYDVR0OBBYEFF5ZFY02\n" +
            "FejgLSsO9DOZppwUhGS4MA0GCSqGSIb3DQEBCwUAA4IBAQCNg76X3tRCtnDi7cT5M8ADMzSUlYvA\n" +
            "CaVYKQ+lB3WBLuqR3wVVJ/zeSGqiR4F59cj6Oij+fk5Fu05n+0nXX/sdirf2RTW1eaA+SzPYXdgA\n" +
            "0u8XJdaPu58q0GCPFpUv24ljzQEr/hZBS9HWlHmkgByecpgrrToOc3jJNIQr8g1NVO/b8RCBFyx8\n" +
            "Ax+Ytp8XNGKbmMnXifBxBnhMCT0DptmZuYsTacZ5qLqEhszChYSM3drYhoNId1HlLLeQbUumbB/8\n" +
            "N1BVVH2LYPTJgKdXTQAzTWiUAss/HD78fe30qI3MDrZ1h4eF+vCHqOwAOiLxQ0Rpb5oMsVCblmoR\n" +
            "+ool1ljG\n" +
            "-----END CERTIFICATE-----";
    private static TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.textview);

    }

    public void click(View view){
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    okhttp();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    void okhttp() throws Exception{
        String path = "https://10.0.2.2:8444/OkHttp/text";
        ByteArrayInputStream baos = new ByteArrayInputStream(chen.getBytes());
        //InputStream iStream = new FileInputStream("chen_client.jks");
        InputStream iStream = getAssets().open("chen_client.bks");
        SSLManagerParams ssl = getSSLManagerParams(baos,iStream);
        OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(ssl.mFactory,ssl.mTrustManager)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                })
                .build();
	//get
        Request request = new Request.Builder().get().url(path).build();
	//post
	/*
	Map map = new HashMap<>();
        map.put("userName", "183********");
        map.put("password", "111111");
        JSONObject jsonObject = new JSONObject(map);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"),jsonObject.toString());
        okhttp3.Request request = new okhttp3.Request.Builder().post(requestBody).url(path).build();
	*/
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                sendMsg(e.toString());
            }

            @Override
            public void onResponse(Call call, okhttp3.Response response) throws IOException {
                sendMsg(response.body().string());
            }
        });
    }


    private void sendMsg(String msg){
        Message message = mHandler.obtainMessage();
        message.obj = msg;
        mHandler.sendMessage(message);
    }


    static class SSLManagerParams{
        public SSLSocketFactory mFactory;
        public X509TrustManager mTrustManager;
    }

    SSLManagerParams getSSLManagerParams(InputStream cer,InputStream bks){
        SSLManagerParams ssl = new SSLManagerParams();
        try{
            //初始化SSLContext
            SSLContext context = SSLContext.getInstance("TLS");//TLS和SSL都可以
            // 1.导入证书
            Certificate certificate = CertificateFactory.getInstance("X.509")
                    .generateCertificate(cer);
            // 2.证书导入密钥库
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setCertificateEntry("srca", certificate);
            // 3.把密钥库放入信任管理器
            TrustManagerFactory trustManagerFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            // 4.生成信任管理器
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            //初始化keystore
            KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            clientKeyStore.load(bks, "123456".toCharArray());
            //将keystore放入密钥管理器
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, "123456".toCharArray());
            //生成密钥管理器
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            // 将信任管理器设置到SSLContext
            context.init(keyManagers, trustManagers, new SecureRandom());

            ssl.mFactory = context.getSocketFactory();
            ssl.mTrustManager = chooseX509TrustManager(trustManagers);

            return ssl;
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static X509TrustManager chooseX509TrustManager(TrustManager[] trustManagers)
    {
        for (TrustManager trustManager : trustManagers)
        {
            if (trustManager instanceof X509TrustManager)
            {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }

}
好了,HPPTS全面解析到这里就结束了。以上的三种网络请求代码为了便于理解,并没有做合法性检测与封装,下面我提供了一个将三种https请求的简单封装工程。

下载:CSDN

参考:http://blog.csdn.net/lmj623565791/article/details/48129405



你可能感兴趣的:(Android,https,双向认证,SSL)