一、浏览器A发送一个请求A到服务器A,首先会经过TCP的三次握手,这个先不关心,然后就是SSL/TLS握手,(SSL和TLS都是网络安全套接字的意思,TLS是新版本,最新的已经到了TLS1.3了),这里只总结关键步骤,详细流程还是得查询一下资料,浏览器A会首先要求服务器A发送一个有效的证书链(根证书-》中间证书-》服务器证书)过来,证书链要有CA颁发机构,比如A,除了根证书外每个证书都得有公钥,及公钥签名及其他信息。
说明:根证书的公钥是浏览器内置的,表明我这个浏览器就默认信任这些证书,自己也可以安装,但是要慎重,而且浏览器会定时更新信任列表,这些都是浏览器自己做的,不用关心。需要理解的是这个公钥内置,全世界的CA机构就那些,全部都存在浏览器里面了,这个就是整个信任链的开始,根证书里面会有一个签名,业内术语叫做自签名,就是自己签名,自己验签,CA机构A拿自己的私钥A对自己的公钥A签名,然后浏览器根据根证书里面的机构名A,拿到内置的公钥A,验证这个签名,能通过,表明这个根证书是值得信任的。
信任了这个根证书之后,还需要验证中间证书B的签名B,这个中间证书签名B是根证书的私钥A对中间证书公钥B得出来的签名,还是拿内置的经过验证的公钥A来验签,通过就说明这个中间证书可信,拿到中间证书的公钥B,来验证服务器证书C的签名C,没错这个签名C就是中间证书的私钥B对服务器证书的公钥C签出来的,验证通过后,浏览器A就可以相信跟服务器A之间的通信是加密的值得可信的,不会有中间人在中间作梗,HTTPS的作用就这些,保证传输安全,不被人篡改。
再说明:浏览器和服务器SSL握手成功之后,由于非对称加密算法的低效率,传输内容的加密采用的是对称加密,而非对称加密只会加密对称加密使用的秘钥。然后浏览器还会对整个内容进行Hash签名(主要是快),保证数据的完整性。
三说明:SSL只能保证数据不被篡改,但是不能保证请求A就是用户A发出来的,用户B可以伪造,也不能保证服务器的运维开发人员通过技术手段拿到一些个人信息,这时就需要服务器来设计一套加解密机制保证应用信息的绝对安全,就是只有用户A,和处理请求的服务器A,以及绝对有责任的开发运维A知道这些信息,无关的运维人员即便截取到也是密文。
到这里浏览器与服务器之间的SSL就算是总结完了。
二、java客户端A发送一个请求A到服务器A,这里的区别就是java需要自己加载信任的根证书,需要自己安装到一个专门的jks或者通用的pkcs12/pfx/p12的证书仓库里面然后手动load,这个就是trustStore(值得信任的公钥库),然后如果还有双向认证呢,还需要加载一个keyStore,就是私钥库。
手撸一下代码(windows系统可以直接使用git bash自带的 openSSL,省的找包安装了):
1,生成一对根证书公私钥:
bash
openssl req -x509 -new -newkey rsa:2048 -nodes -keyout rootCA.key -out rootCA.crt -days 3650
这里会让输入一些证书的信息如下:C=CN(国家)ST=Beijing(省份)L=Beijing(城市)O=MyOrganization(机构)OU=ITDepartment(部门)CN=MyPrivateRootCA(名称)emailAddress=admin@myorg.com(联系方式)自己测试就随便造个数据,这里私钥和公钥证书都一次生成了。
2,生成一个服务器证书的私钥key:
bash
openssl req -new -key server.key -out server.csr -config server.csr.cnf
这里只生成了一个私钥key,公钥证书需要拿根证书来签名生成。
3,生成一个证书申请文件csr:
bash
openssl req -new -key server.key -out server.csr -config server.csr.cnf
这里需要一个server.csr.cnf文件 内容如下:
bash
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
# req_extensions = req_ext
[dn]
# 以下信息应与你的组织或测试环境匹配
C = CN
ST = Beijing
L = Beijing
O = MyOrganization
OU = WebServer
# 最重要的字段:必须是你访问服务器时使用的域名
CN = localhost
# !!! 关键部分:主题备用名称(SAN)!!!
[req_ext]
subjectAltName = @alt_names
[alt_names]
# 列出所有需要被证书认可的访问方式
DNS.1 = localhost
DNS.2 = localhost.com
IP.1 = 127.0.0.1
# 你可以继续添加更多域名或IP,例如:
# DNS.3 = www.example.com
# IP.2 = 192.168.1.100
4,拿根证书去签名生成一个服务器证书文件,包含公钥及CA机构信息还有公钥签名等
bash
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile server.csr.cnf -extensions req_ext
5,由于是自生成的根证书还需要将其安装到信任的CA列表里面,使用JAVA 的 keytool命令:
bash
keytool -import -trustcacerts -alias myCaRoot -file rootCA.crt -keystore my-truststore.jks -storepass 123456 -noprompt
6,将自己生成的服务器证书和私钥配置在nginx 里面。
bash
server {
listen 8443 ssl;
server_name localhost;
ssl_certificate ssl/server.crt;
ssl_certificate_key ssl/server.key;
location / {
root html;
index index.html index.htm;
}
}
7,浏览器访问 会提示不受信任。
8,JAVA代码访问,注意这个 localhost 的匹配:
java
package org.example;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
public class SSLClientTest {
@Test
void testHttp() {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
ClassicHttpRequest httpGet = ClassicRequestBuilder.get("http://127.0.0.1:8848").build();
httpclient.execute(httpGet, response -> {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final HttpEntity entity = response.getEntity();
String string = EntityUtils.toString(entity);
System.out.println(string);
EntityUtils.consume(entity);
return null;
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
void testHttps() throws Exception {
final PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
.setTlsConfigResolver(host -> TlsConfig.DEFAULT)
.setTlsSocketStrategy(getDefaultClientTlsStrategy(loadTrustStore()))
.build();
try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) {
ClassicHttpRequest request = ClassicRequestBuilder.get("https://localhost:8443/").build();
System.out.println("Executing request " + request);
httpclient.execute(request, response -> {
System.out.println("----------------------------------------");
System.out.println(request + "->" + new StatusLine(response));
String string = EntityUtils.toString(response.getEntity());
System.out.println(string);
EntityUtils.consume(response.getEntity());
return null;
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private DefaultClientTlsStrategy getDefaultClientTlsStrategy(KeyStore keyStore)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return new DefaultClientTlsStrategy(SSLContextBuilder.create().loadTrustMaterial(keyStore, null).build());
}
private KeyStore loadTrustStore()
throws Exception {
KeyStore instance = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream is = Files.newInputStream(Paths.get("D:/application/nginx-1.28.0/conf/ssl/my-truststore.jks"))) {
if (is == null) {
throw new RuntimeException("无法找到信任库文件: my-truststore.jks");
}
instance.load(is, "123456".toCharArray()); // 使用你设置的密码
}
System.out.println(instance.size());
return instance;
}
}
三、还有一个需要记录的就是各种文件格式:
pem 就是个base64的文件格式,可以是公钥 也可以是私钥。
der 是个二进制的文件,可以是公钥,也可以是私钥。
crt/cer 一般就是指证书,包含公钥及CA机构信息还有公钥签名等
jks java专用的公钥库、私钥库,
pkcs12/pfx/p12 通用的公钥库、私钥库
csr 证书签名请求。这个是申请证书的时候提供的东西,由自己的私钥签名信息 和公钥信息,证明我拥有该对秘钥。
以上搞一遍基本原理就能了解个七七八八了。