前提:
客户端:1.信任CA机构,获取CA公钥
服务端:1.申请CA证书,获取CA公钥
流程:
1.服务端:将证书信息转发至客户端(给用于加密的公钥)
2.客户端:用CA公钥解证书信息,获取服务器的公钥;生成用于解密密文的对称加密密钥并使用服务端公钥加密;
3.服务端:使用私钥解出客户端用于解密密文的公钥,并保存。
客户端&服务端:正常通信。
个人签发的证书和CA签发的证书一样,都是新人链的起始点,是根证书。
功能:客户端为防止信息泄露,主动进行数字证书校验和混合加密,是客户端的单向认证。
1.HTTPS优势
-
内容加密:采用混合加密技术,中间者无法直接查看明文内容
-
验证身份:通过证书认证客户端访问的是自己的服务器
-
保护数据完整性:防止传输的内容被中间人冒充或者篡改
解释:
-
对称加密:加解密使用的密钥相同。
-
非对称加密:加解密使用的密钥不同。一般提供给外部的是公钥,自身保留的是私钥。公钥加密只能私钥解密,公钥只能解密私钥密文;私钥加密只能公钥解密,私钥只能解密公钥密文。
-
混合加密:结合非对称加密和对称加密技术。客户端生成一个随机数作为对称加密的密钥,对传输数据进行加密,然后使用非对称加密的公钥(服务端提供的公钥)再对密钥进行加密,所以网络上传输的数据是被密钥加密后的密文和用公钥加密后的加密密钥,因此即使被黑客截取,由于没有公钥对应的私钥,无法获取到加密明文的密钥,便无法获取到明文数据。
-
数字摘要:通过单向hash函数对原文进行哈希,将需加密的明文“摘要”成一串固定长度(如128bit)的密文,不同的明文摘要成的密文其结果总是不相同,同样的明文其摘要必定一致,并且即使知道了摘要也不能反推出明文。
-
数字签名技术:先进行数字摘要,再进行公钥加密。
2.HTTPS流程
1.client向server发送https请求,连接到server的443端口。
2.服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对(非对称加密的)公钥和私钥。
3.服务端传送证书给客户端
证书包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。
4.客户端解析证书
这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(秘钥)。然后用证书对该随机值进行加密。然后再使用服务端公钥对密钥进行加密,该密钥便是本次会话的两端进行通信的唯一密钥。
5.传送加密信息
这部分传送的是用公钥加密后的密钥和密钥加密后的明文,目的就是让服务端得,
6.服务段加解密信息
服务端用私钥解密密钥,以后客户端和服务端的通信就可以通过这个密钥来进行加密解密了。然后把内容通过该密钥进行对称解密获得明文。然后用密钥对响应明文加密。
7.传输加密后的响应信息
这部分信息是服务端用密钥加密后的信息。
8.客户端解密信息
客户端用之前生成的密钥解密服务端传过来的信息,于是获取了解密后的内容。
3.安全性分析
1.客户端使用服务端公钥对加密密钥进行加密,只有服务端能用私钥进行解密获得客户端的明文加密密钥。黑客即使截取报文,因无法获得密钥,则无法破解明文。
2.服务端如何安全的把数字证书安全的传给客户端,被掉包了怎么办?比如下图,数字证书被中间人截取,修改服务端公钥为自己的公钥,伪装成服务端和客户端进行通信,导致信息泄露。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4ajo27J-1606968411804)(http://192.168.58.141:23333/doc/course/https/ca证书劫持.png)]
问题的关键在于:服务端的公钥被掉包了,只要保证服务端的公钥能安全的传输到客户端,那么中间人即使截取数据报文也无法进行加解密。这依赖于第三方权威机构发布的数字证书。
服务端的数字证书主要包含:
服务端公钥
服务端域名
权威机构(CA)的信息
CA数字签名(先对服务端公钥、CA信息、服务端域名进行数字摘要,然后用CA私钥对摘要进行加密得到数字签名)
数字摘要计算方法
? 当客户端收到服务端数字证书之后,使用本地配置的CA的公钥,对证书中的数字签名进行解密,得到数字摘要
。然后根据签名计算方法对服务端公钥、CA信息、服务端域名进行数字摘要,两个数字摘要对比,如果相同则说明证书一定是服务器下发的。
? 因为中间人可以拥有CA公钥,能够解密证书内容并篡改,但是篡改之后需要用CA私钥加密,而中间人不可能获得CA私钥。强行加密只会导致客户端无法解密。
4.HTTPS图示
CA证书认证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IzXub65p-1606968411806)(http://192.168.58.141:23333/doc/course/https/CA数字证书认证.png)]
5.个人签发证书使用
个人签发的证书和CA签发的证书一样,都是信任链的起始点,是根证书。
5.1.功能
客户端为防止信息泄露,主动进行数字证书校验和混合加密,单向认证。
5.2.数字证书配置方法
1.服务端生成密钥库(server.keystore)
keytool -genkey -alias serverkey -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore server.keystore -validity 3650 -storepass 123456 -dname “CN=localhost,OU=DEP,O=CN,L=BJ,ST=BJ,C=CN”
其中CN的值是域名,以支持客户端进行域名校验。
2.服务端引用密钥库发布HTTPS接口
3.服务端根据密钥库生成公钥证书(server.crt),发给客户端
keytool -export -alias serverkey -keystore server.keystore -file server.crt
4.客户端把服务端公约证书导入到客户端密钥库(client.keystore)中
keytool -import -file server.crt -keystore client.keystore
5.客户端配置信任的密钥库(client.keystore)
5.3.SpringBoot服务端配置
仅需要在application.properties中添加如下信息:
#密钥库 server.ssl.key-store=classpath:server.keystore # 密钥库密码 server.ssl.key-store-password=123456 server.ssl.keyStoreType=PKCS12 server.ssl.keyAlias=serverkey
5.4.HttpClient客户端配置
配置方法一
SendHttpsUtil:
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
public class SendHttps {
static String trustStorePath = PathUtil.classPath() + "client.keystore";
static String trustStorePassword = "123456";
static String trustStoreType = "JKS";
static CloseableHttpClient httpClient;
static {
//初始化Trust Manager,即配置信任的数字证书,该数字证书可由服务端提供的公钥证书生成
TrustManagerFactory trustFactory = null;
KeyStore tsstore;
try {
trustFactory = TrustManagerFactory.getInstance("SunX509");
tsstore = KeyStore.getInstance(trustStoreType);
tsstore.load(new FileInputStream(new File(trustStorePath)),
trustStorePassword.toCharArray());
trustFactory.init(tsstore);
} catch (Exception e) {
e.printStackTrace();
}
TrustManager[] trustManagers = trustFactory.getTrustManagers();
// 构造SSL上下文对象
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
} catch (Exception e) {
e.printStackTrace();
}
//设置规则限制
SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1","TLSv1.1","TLSv1.2"},null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
//注册
Registry<ConnectionSocketFactory> socketFactoryRegistry = null;
socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", ssf).build();
//池化管理
PoolingHttpClientConnectionManager connManager = new
PoolingHttpClientConnectionManager(socketFactoryRegistry);
//创建httpClient
httpClient = HttpClients.custom().setConnectionManager(connManager).build();
}
public static String httpsGet(String url) {
HttpGet httpGet = new HttpGet(url);
try {
CloseableHttpResponse response = httpClient.execute(httpGet);
String result = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println(result);
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//因为证书中CN中定义了域名为localhost,所以这里只能使用域名localhost
public static void main(String[] args) {
System.out.println(SendHttps.httpsGet("https://localhost:8080/hello"));
}
}
自定义HostnameVerifier实现类HostnameVerifierImpl,不进行域名校验
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class HostnameVerifierImpl implements HostnameVerifier {
@Override
public boolean verify(String var1, SSLSession sslSession) {
return true;
}
}
配置方法二
import com.zwj.test.util.PathUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
public class TestClient {
private static SSLContext sslcontext;
public static void main(String[] args) throws Exception {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File(PathUtil.classPath() + "client.keystore"));
// 加载keyStore
trustStore.load(instream, "123456".toCharArray());
// TrustSelfSignedStrategy是信任所有个人签发的根证书的策略
// sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore,
new TrustSelfSignedStrategy()).build();
sslcontext = SSLContexts.custom().loadTrustMaterial(
trustStore, null).build();
} catch (Exception e) {
e.printStackTrace();
}
//使用默认的域名校验,可自定义HostnameVerifier实现类
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpClient = HttpClients
.custom()
.setSSLSocketFactory(sslsf)
// 异常重试机制 3次 (网络层面上的)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3,true))
.build();
//因为证书中CN中定义了域名为localhost,所以这里只能使用域名localhost
HttpGet httpGet = new HttpGet("https://localhost:8080/hello");
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println(EntityUtils.toString(response.getEntity(), "utf-8"));
}
}
其中loadTrustMaterial方法如下:
如果trustStrategy为NULL,则和配置方法一的逻辑是完全一样的。
public SSLContextBuilder loadTrustMaterial(final KeyStore truststore,
final TrustStrategy trustStrategy)
throws NoSuchAlgorithmException, KeyStoreException {
final TrustManagerFactory tmfactory = TrustManagerFactory
.getInstance(trustManagerFactoryAlgorithm == null ?
TrustManagerFactory.getDefaultAlgorithm():trustManagerFactoryAlgorithm);
tmfactory.init(truststore);
final TrustManager[] tms = tmfactory.getTrustManagers();
if (tms != null) {
if (trustStrategy != null) {
for (int i = 0; i < tms.length; i++) {
final TrustManager tm = tms[i];
if (tm instanceof X509TrustManager) {
tms[i] = new TrustManagerDelegate(
(X509TrustManager) tm, trustStrategy);
}
}
}
for (final TrustManager tm : tms) {
this.trustManagers.add(tm);
}
}
return this;
}
5.5.安全性分析
(1)服务端公钥证书被调包
客户端异常日志:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: Certificate signature validation failed
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:355)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:359)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:381)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
at com.zwj.test.client.util.SendHttps.httpsGet(SendHttps.java:71)
at com.zwj.test.client.util.SendHttps.main(SendHttps.java:82)
Caused by: sun.security.validator.ValidatorException: Certificate signature validation failed
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:212)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
… 21 more
Caused by: java.security.SignatureException: Signature does not match.
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:449)
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:392)
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:210)
… 26 more
5.6.信任所有证书
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
//信任所有证书
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
//ALLOW_ALL_HOSTNAME_VERIFIER:这个主机名验证器基本上是关闭主机名验证的,实现的是一个空操作,并且
//不会抛出javax.net.ssl.SSLException异常。
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" }, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();