HTTPS协议是安全通信过程中常用的应用层协议,本质上说就是HTTP+SSL/TLS的融合版本。SSL/TLS是基于TCP的传输层协议,它的握手协议主要分为三个阶段:
1.协商算法:
客户端提供服务端自身支持的加密算法并发送一个随机数RNC,服务端提供客户端自身支持的加密算法并发送一个随机数RNS,双方商榷得到一个共同支持的加密算法
2.验证证书:
单向认证情况下,只需要客户端验证服务端的身份。客户端在接受到服务端的证书时,可以向CA(证书认证中心)发送验证请求,CA会返回证书可用性响应。当然 很多时候,我们在通信过程中使用的是自身签发的证书,客户端可以通过本地的信任库来验证证书。
3.生成秘钥
客户端再生成一个随机数PMS,并通过服务端下发的公钥加密发送给服务端,服务端通过私钥解密得到PMS。此时客户端与服务端双方有三个随机数RNC,RNS,PMS。通过这三个随机数,得到本次通信的对称秘钥。
PS:
1.电子商务系统或者金融系统中一般会采用双向认证证书,即客户端认证服务端之外,服务端也需要认证客户端。比如网银的U盾,支付宝的数字证书,都是网银或者支付宝认证客户端的手段之一。
2.每次SSL/TLS握手生成的对称秘钥都会不同。
java 中实现SSL/TLS通信的代码如下:
服务端:
package net.flyingfat.Ssl;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class ServerSsl {
public static void main(String[] args) throws Exception {
sslSocketServer();
}
// 启动一个ssl server socket
// 配置了证书, 所以不会抛出异常
public static void sslSocketServer() throws Exception {
// key store相关信息
String keyName = "cmkey";
char[] keyStorePwd = "123456".toCharArray();
char[] keyPwd = "123456".toCharArray();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
// 装载当前目录下的key store. 可用jdk中的keytool工具生成keystore
InputStream in = null;
keyStore.load(in = ServerSsl.class.getClassLoader().getResourceAsStream(
keyName), keyPwd);
in.close();
// 初始化key manager factory
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm());
kmf.init(keyStore, keyPwd);
// 初始化ssl context
SSLContext context = SSLContext.getInstance("SSL");
context.init(kmf.getKeyManagers(),
new TrustManager[] { new MyX509TrustManager() },
new SecureRandom());
// 监听和接收客户端连接
SSLServerSocketFactory factory = context.getServerSocketFactory();
SSLServerSocket server = (SSLServerSocket) factory
.createServerSocket(10002);
System.out.println("ok");
Socket client = server.accept();
System.out.println(client.getRemoteSocketAddress());
// 向客户端发送接收到的字节序列
OutputStream output = client.getOutputStream();
// 当一个普通 socket 连接上来, 这里会抛出异常
// Exception in thread "main" javax.net.ssl.SSLException: Unrecognized
// SSL message, plaintext connection?
InputStream input = client.getInputStream();
byte[] buf = new byte[1024];
int len = input.read(buf);
System.out.println("received: " + new String(buf, 0, len));
output.write(buf, 0, len);
output.flush();
output.close();
input.close();
// 关闭socket连接
client.close();
server.close();
}
public static class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
System.out.println("click trust");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
System.out.println("server trust");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
package net.flyingfat.Ssl;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class ClientSsl {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
sslSocket2();
}
// 这种情况是使用空证书??
public static void sslSocket2() throws Exception {
SSLContext context = SSLContext.getInstance("SSL");
context.init(null,
new TrustManager[] { new ServerSsl.MyX509TrustManager() },
new SecureRandom());
SSLSocketFactory factory = context.getSocketFactory();
SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002);
System.out.println("ok");
OutputStream output = s.getOutputStream();
InputStream input = s.getInputStream();
output.write("alert".getBytes());
System.out.println("sent: alert");
output.flush();
byte[] buf = new byte[1024];
int len = input.read(buf);
System.out.println("received:" + new String(buf, 0, len));
}
}
模拟浏览器访问百度页面:
package net.flyingfat.Ssl;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.SecureRandom;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
public class HttpsConnection {
public static void main(String[] args) throws Exception {
SSLContext sslContext=SSLContext.getInstance("TLS");
sslContext.init(null, null, new SecureRandom());
SSLSocketFactory sslSocketFactory=sslContext.getSocketFactory();
URL url=new URL("https://www.baidu.com/");
HttpsURLConnection conn=(HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslSocketFactory);
conn.connect();
InputStream input=conn.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(input));
String line="";
while((line=reader.readLine())!=null){
System.out.println(line);
}
reader.close();
input.close();
conn.disconnect();
}
}
SSLContext 中有两个容易混淆的接口: KeyManager(秘钥库)与TrustManager(信任库)
密钥库和信任库都是密钥库。惟一的区别在于它们的使用方式:
密钥库通常包含私钥和公钥证书,这些信息在客户端尝试连接到服务器时提供给客户端。
信任库通常包含受信任的根证书颁发机构 (CA) 证书。这些证书用于在建立出站连接时确定目标服务器是否值得信赖。
关于https的性能
https带来安全性的同时,也带来了性能上的开销,主要体现在两个方面:
1.二次握手带来的网络延时,因为SSL/TLS是基于TCP的,所以首先进行一次TCP的握手,接着再进行SSL/TLS本身的握手
2.握手阶段使用到公钥与私钥的加解密带来的服务器CPU性能消耗
虽然有性能上的开销,但https也是大势所趋,http2.0默认就是基于https的。
参考文章:
http://410063005.iteye.com/blog/1751243
http://network.51cto.com/art/201505/474980.htm
参考书籍:
java 加密与解密技术