什么是 HTTPS?
1990 年,我们所知道的互联网诞生了。从一开始,它就使用超文本传输协议(HTTP)在全球范围内移动信息。这就是为什么网址以 HTTP 开头的原因。
普通的旧 HTTP 不安全,因为它以_纯文本格式_传输信息。这意味着任何拦截流量的人都可以读取它。这不仅包括监视咖啡店 WiFi 的黑客,还包括您的互联网服务提供商(ISP)。就像总机操作员可以收听电话一样。
但是人们很快决定要使用 Internet 来获取敏感数据(例如信用卡号),因此我们不得不想出一种使 HTTP 安全的方法,以便在浏览器和浏览器之间缩放时,没人能看到您的信用卡号。网络服务器。
因此在 1994 年,Netscape Communications 通过某种加密增强了 HTTP 。本质上,他们将新的名为 安全套接字层(SSL) 的加密协议与原始 HTTP 结合在一起。这被称为“ HTTP over SSL”或“ HTTP Secure”。否则称为 HTTPS。
如今,所有网站中超过 50%是 HTTPS。自爱德华·斯诺登(Edward Snowden)透露国家安全局(NSA)监视每个人的互联网流量以来,该数字在过去几年中一直在急剧增长。
许多人都说过,这个想法是将整个 Internet 迁移到一个完全 HTTPS 环境中,默认情况下所有网站流量都经过加密。
为什么要加密整个互联网?
HTTPS 在保护隐私方面和安全方面都发挥了作用。防止黑客读取您的数据或将自己的代码注入您的 Web 会话(这是 HTTPS 可以阻止的)是一回事,但隐私是另一方面。
我们知道,ISP,政府和大数据收集公司只是喜欢窥探并存储我们的流量以了解什么。当然,您可能不认为自己在乎。也就是说,直到您浏览有关个人医疗状况的信息或有关青少年怀孕的建议为止。那是谁的事。该信息对某人总是有用的,这就是他们想要并保留它的原因。永远。
这就是为什么许多网站(例如 TipTopSecurity)选择加密您的流量,即使您没有发送敏感信息也是如此。因为我们认为您的在线行为应保持尽可能 私密 。(注: 某互联网大佬如此说: 中国人喜欢用隐私换取便利)
HTTPS 如何工作
- 您的浏览器连接到网站服务器并请求连接。
- 服务器向您发送其 公钥 。它会将其私钥保密。
- 您的浏览器会生成第三个密钥,称为_会话密钥_。
- 会话密钥 由计算机使用从服务器获得的公共密钥加密
- 然后,加密的会话密钥将与服务器共享。
- 服务器使用私钥对从您收到的会话密钥进行解密。现在,两端都有您的计算机生成的会话密钥。
- 公钥加密终止并被对称加密代替。
- 现在,您仅使用对称加密与服务器进行会话,这就是它的方式,直到您离开网站为止。
其中认证方式又分为 单向认证 和 双向认证
单向认证
流程 | 内容 | ||
---|---|---|---|
客户端发起 Client Hello | 客户端支持的 SSL/TLS 协议版本列表; 支持的对称加密算法列表; 客户端生成的随机数 A。 | ||
服务器端回应 Server Hello | 1. SSL/TLS 版本。服务器会在客户端支持的协议和服务器自己支持的协议中,选择双方都支持的 SSL/TLS 的最高版本,作为双方使用的 SSL/TLS 版本。如果客户端的 SSL/TLS 版本服务器都不支持,则不允许访问; 2. 与1类似,选择双方都支持的最安全的加密算法; 3. 从服务器密钥库中取出的证书; 4. 服务器端生成的 随机数 B。 | ||
客户端回应 | 1. 检查证书是否过期 2. 检查 证书是否已经被吊销 。有 CRL 和 OCSP 两种检查方法。CRL 即证书吊销列表,证书的属性里面会有一个 CRL 分发点属性,这个属性会包含了一个 url 地址,证书的签发机构会将被吊销的证书列表展现在这个 url 地址中;OCSP 是在线证书状态检查协议,客户端直接向证书签发机构发起查询请求以确认该证书是否有效。 3. 证书是否可信。浏览器会有一个信任库,里面保存了该客户端信任的 CA(证书签发机构) 的证书,如果收到的证书签发机构不在信任库中,则浏览器会提示用户证书不可信。假如是 Java 程序,需要程序配置信任库文件,以判断证书是否可信,如果没设置,则默认使用 jdk 自带的证书库(jre\lib\security\cacerts,默认密码 changeit)。如果证书或签发机构的证书不在信任库中,则认为不安全,程序会报错。(你可以在程序中设置信任所有证书,不过这样并不安全)。 4. 检查收到的证书中的域名与请求的域名是否一致。若客户端是程序,这一项可配置不检查。若为浏览器,则会出现警告,用户也可以跳过。证书验证通过后,客户端使用特定的方法又生成一个 随机数 c ,这个随机数有专门的名称“pre-master key”。接着客户端会用证书的公钥对“pre-master key”加密,然后发给服务器。 | ||
服务器回应 | 服务器使用密钥库中的私钥解密后,得到这个随机数 c。此时,服务端和客户端都拿到了随机数 a、b、c ,双方通过这 3 个随机数使用相同的 DH 密钥交换算法 计算得到了相同的 对称加密的密钥 。这个密钥就作为后续数据传输时对称加密使用的密钥。 服务器回应客户端,握手结束,可以采用对称加密传输数据了。 |
双向认证
单向验证过程中,客户端会验证自己访问的服务器,服务器对来访的客户端身份不做任何限制。
如果服务器需要限制客户端的身份,则可以选择开启服务端验证,这就是双向验证。
见 Java 中的 KeyStore 和 TrustStore 里面的 trust 和 key 就是双向的验证逻辑。
客户端配置
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
// 配置信任链
.loadTrustMaterial(null, acceptingTrustStrategy)
// 配置证书
.loadKeyMaterial(keyStore("classpath:client_keystore.jks", password), password)
.build();
spring-boot 服务器配置
server:
port: 8443
ssl:
key-store: server.jks
key-store-password: password
key-store-type: PKCS12
trust-store: server_trust.jks
trust-store-type: JKS
trust-store-password: password
#需要认证客户端证书
client-auth: need
总结一下:
HTTPS 其实是有两部分组成:HTTP + SSL / TLS,也就是在 HTTP 上又加了一层处理加密信息的模块。
服务端和客户端建立连接使用非对称加密。
服务端和客户端的信息传输都会使用对称加密,
所以传输的数据都是加密后的数据。
HTTPS 不能做什么
HTTPS 不会:
隐藏您正在访问的网站的_名称_
这是因为网站的名称(也称为“域名”)是使用 DNS(域名服务)发送的,该域名不在 HTTPS 隧道内。在建立安全连接之前发送它。中间的一个窃听者可以看到您要访问的网站的名称(例如 TipTopSecurity.com),他们只是无法读取来回传输的任何实际内容。直到 DNSSEC 完全实施,这种情况才会改变。
保护您避免访问邪恶的网站
HTTPS 不能确保网站本身是安全的。仅仅因为您安全地连接并不意味着您就没有连接到由坏人经营的网站。我们尝试使用受信任的证书颁发机构解决此问题,但是该系统并不完美(敬请期待更多信息)。
提供匿名
HTTPS 不会隐藏您的实际位置或个人身份。您的个人 IP 地址(您在 Internet 上的地址)必须附加到加密数据的外部,因为如果您的 IP 地址也被加密,则 Internet 也不知道将数据发送到哪里。而且,它也不会掩盖您正在访问的网站的身份。您访问的站点仍然了解您在不安全连接上的所有信息。
防止您感染病毒
HTTPS 不是过滤器。可以通过 HTTPS 连接接收病毒和其他恶意软件。如果网络服务器被感染,或者您正在分发恶意软件的恶意网站上,它将像其他所有内容一样在 HTTPS 流中发送。但是,HTTPS _确实可以_防止中间人将恶意软件注入到您的移动流量中。
保护您的计算机免遭黑客攻击
HTTPS 仅在数据在计算机和 Web 服务器之间移动时保护数据。它不为您的实际计算机或服务器本身提供任何保护。这也意味着,如果有恶意软件在连接的一端监视流量,则可以在 HTTPS 流中加密该流量之前和之后读取该流量。
基本上,HTTPS 仅在您的信息通过电线(或空中)流动时保护您的信息。它无法保护您的计算机,您的身份或隐藏您正在访问的站点。HTTPS 只是更安全的互联网的一部分。如果您正在寻找更多的隐私,那么下一步将是 VPN 服务。查看本文,了解有关 VPN 的更多信息。
MITM
Man-in-the-middle attack 中间人攻击
大概就是下图这样
上图展示了中间人理论中一个叫 SSLSniff 的理论
SSLSniff
攻击者在网关截获 SSL 会话,替换服务器公钥证书,将公钥 PKey
换成自己的公钥 PKey
,欺骗客户端。客户端使用 PKey
加密信息并发送会话,中间人用私钥 Skey
解密客户端返回会话,从而劫持会话。同时,中间人用 PKey
加密明文会话并返回服务器
过程如下:
Attacker
截获了客户端的say hello
,然后自己冒充 Server, 可以把publicKey_attacker
返回给客户端,取得客户端的信任,至此Attacker
与客户端建立了安全连接。Attacker
冒充客户端向服务器发送say hello
,至此Attacker
与服务器建立了安全连接。
这种攻击会存在一个问题会被感知到,就是 Attacker
的证书是伪造的 不受信任 的证书。
不受信任 - CA
为什么伪造的证书无法通过验证?这就是 CA 的功劳,CA 是一个权威的第三方机构,经过 CA 认证的证书才会通过客户端浏览器的验证,浏览器怎么知道某个证书是否被 CA 认证过?浏览器和操作系统都会内置一些可信的根证书颁发机构,也就是说这些机构的权威性是由浏览器或操作系统保证的。
证书链
上面讲到了 CA 在验证证书中起到了非常大的作用。 这里又引申出另一个问题。
如何验证证书。
这里就涉及到证书链的概念。
- 最上层为 root,也就是通常所说的 CA ,用来颁发证书;
- 最下层为 end-user,对应 每个网站 购买使用的证书;
- 中间一层为 intermediates,是二级 CA ,这一层可以继续划分为多层,用来帮助 root 给 end-user 颁发证书,这样 root 只需向 intermediates 颁发证书;
只有当整个证书链上的证书都有效时,才会认定当前证书合法 。
- 客户端获取到了站点证书,拿到了站点的公钥;
- 要验证站点可信后,才能使用其公钥,因此客户端找到其站点证书颁发者的信息;
- 站点证书的颁发者验证了服务端站点是可信的,但客户端依然不清楚该颁发者是否可信;
- 再往上回溯,找到了认证了中间证书商的源头证书颁发者。由于源头的证书颁发者非常少,我们浏览器之前就认识了,因此可以认为根证书颁发者是可信的;
- 一路倒推,证书颁发者可信,那么它所颁发的所有站点也是可信的,最终确定了我们所访问的服务端是可信的;
- 客户端使用证书中的公钥,继续完成 TLS 的握手过程。
自签名证书
难道我们每一次都需要用这么复杂的流程,去申请证书,然后才能使用 https 嘛?
当然不是, 上面说的证书,是供网络访问的。硕大的网络上,谁都不敢直接相信别人,所以,只能相信第三方。
但如果是我们的开发环境,或者私人环境,我们当然可以信任啦。
这个时候,就可以用到了 自签名证书 的东西。
创建根证书
1、 生成私钥
$ openssl genrsa -out ca.key 2048
# [-out],指定生成文件名; [-des|-des3|-idea],采用什么加密算法来加密我们的密钥,使用其中任一参数,在生成密钥的过程都会要求输入密码,缺省情况无需输入密码,我们这里就没有加密; [numbits],指明产生参数的长度。必须是最后一个参数。我们这里是 2048,缺省则产生 512bit 长的参数(不过我看配置文件默认好像也是2048?)。
2、生成证书请求文件
$ openssl req -new -key ca.key -out ca.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
## If you enter '.', the field will be left blank.
Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:myca
Email Address []:
An optional company name []: [-new],生成证书请求文件,它会提示用户关于一些字段的值。可以通过 openssl.cnf 来查看默认会询问那些字段,最大/小长度限制等,如果[-key] 没有指定,则会按照默认配置文件生成新的私钥>; [-key],指定已有的密钥文件; [-out],指定生成文件名。
其中 Common Name 必填, 根证书可以是公司名称, 但如果用于服务器必须是域名或者 ip
3、生成自签名证书
$ openssl req -x509 -days 3650 -key ca.key -in ca.csr -out ca.crt
[-x509],req 中的 [-x509] 表示生成一个自签名的证书,而不是一个证书请求;
[-in],x509 表示 in 后面的输入文件为证书请求文件,默认是证书文件;
[-key],用于提供自签名时的私钥文件。
创建用户证书
1、生成私钥
$ openssl genrsa -out server.key 2048
2、生成客户请求
$ openssl req -new -key server.key -out server.csr
此后输入密码、server证书信息完成,也可以命令行指定各类参数
3、生成证书
$ openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key
[-cert],指定 CA 证书;
[-keyfile],指定私钥。
最后的步骤
浏览器 (也可以是自己写的客户端) 导入根证书
nginx (或者是任意负载均衡服务器)导入自签名证书。就可以了~~~
小工具
一键生成证书
SSL证书生成
https://github.com/michaelliao/itranswarp.js/blob/master/conf/ssl/gencert.sh
keytool 生成
https://www.jb51.net/article/128162.htm
https://www.baeldung.com/spring-boot-https-self-signed-certificate
HttpClient 自定义 SSL 上下文
大家先看代码
//Creating SSLContextBuilder object
SSLContextBuilder SSLBuilder = SSLContexts.custom();
//Loading the Keystore file
File file = new File("mykeystore.jks");
SSLBuilder = SSLBuilder.loadTrustMaterial(file,
"changeit".toCharArray());
//Building the SSLContext usiong the build() method
SSLContext sslcontext = SSLBuilder.build();
//Creating SSLConnectionSocketFactory object
SSLConnectionSocketFactory sslConSocFactory = new SSLConnectionSocketFactory(sslcontext, new NoopHostnameVerifier());
//Creating HttpClientBuilder
HttpClientBuilder clientbuilder = HttpClients.custom();
//Setting the SSLConnectionSocketFactory
clientbuilder = clientbuilder.setSSLSocketFactory(sslConSocFactory);
//Building the CloseableHttpClient
CloseableHttpClient httpclient = clientbuilder.build();
代码中有两个关键的方法
- SSLBuilder.loadTrustMaterial
- new SSLConnectionSocketFactory()
我们一个个的讲解
loadTrustMaterial
顾命思议:加载信任政策
对应者 ssl 建立的哪一步呢。
对应着 客户端回应 。 会检查服务器的证书是不是可用。
loadTrustMaterial 总的来说有两种方法。
但目的都是唯一的,就是通过加载密钥库的文件,从而解密证书,验证证书的信息,从而决定是否需要信任此次的证书。
// jdk_home/jre/lib/security - cacerts 文件 - 后缀为 .jks
SSLContextBuilder loadTrustMaterial(File file, char[] storePassword, TrustStrategy trustStrategy)
例子上文中有,就不重复了
// 将密钥库的文件转为 bytes, 然后使用 KeyStore.load() 加载
SSLContextBuilder loadTrustMaterial(KeyStore truststore, TrustStrategy trustStrategy)
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// 分别是 cacerts 文件的 byte 形式, 和密码 "changeit" ( 默认密码 )的 char[] 形式
trustStore.load(getKeyStoreIn(), KEY_STORE_KEY.toCharArray());
sc = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
这里还有一个信任策略的问题, 也就是 TrustSelfSignedStrategy .
这个信任策略代表什么呢? 代表我们是可以信任 自签名的证书 的呦。
自签名的证书上文中有详细的描述。这里就不多说了。
SSLConnectionSocketFactory
这里主要是提一点,就是 NoopHostnameVerifier 。
什么是 NoopHostnameVerifier 呢?
我们证书中有一部分 SAN (Subject Alternative Name)的内容。
这里会指定目标 ip 或者域名
如下图
假设, 你发送一个请求到 192.168.6.232, 映射了 192.168.5.235,
那么,如果不设置 NoopHostnameVerifier, 你的程序就会报错 192.168.6.232 不符合 192.168.5.235.
如果设置了, 就不会校验这个信息。
Note
Java 中的 KeyStore 和 TrustStore
Difference Between a Java Keystore and a Truststore
Java 的 keytool 工具的使用
NOTE
Java 中的 KeyStore 是一串证书,不是单纯的一个证书。库的格式有 pkcs12 和 jks。不能直接使用 crt、cer
Https 证书的格式和编码
参考
https://www.baeldung.com/httpclient-ssl
https://www.yiibai.com/apache_httpclient/apache_httpclient_custom_ssl_context.html
https://www.gokuweb.com/operation/d95eae05.html
https://curl.haxx.se/docs/sslcerts.html
http://www.smartjava.org/content/how-analyze-java-ssl-errors/
https://www.gokuweb.com/websec/873e23ee.html