HTTPS

HTTPS

第一节 引文

1.1 背景

  HTTPS实际上就是HTTP穿上了SSL/TLS的外套,在学习了计算机网络知识后我们了解了HTTP属于应用层的协议。HTTP协议全称Hyper Text Transfer Protocol,即超文本传输协议,位于TCP/IP四层模型当中的应用层。HTTP协议通过请求/响应的方式,在客户端和服务端之间进行通信。

  而SSL(安全套接字)与TLS(运输层安全)都属于运输层协议。其实SSL与TLS都是上个世纪的产物,最新版本的TLS建立在SSL 3.0协议规范之上。

  HTTP协议的信息传输是完全以明文方式,没有做任何加密,所以数据就是直接暴漏在整个网络中。可想而知如果在传输过程中被不怀好意的人员或程序窃听、劫持或篡改,我们是无能为力的,所以提高HTTP的安全性迫在眉睫。

1.2 发展

  我们可以代入行业技术人员的角度,来思考一下HTTPS这种安全模式的发展过程,可以帮助我们更清晰的去理解其设计和针对性。

  我们来解决HTTP的安全问题,首先最容易想到的就是加密,我们可以通过暗号的方式来交流信息,这样应该就不担心被别人轻易的读取到真实数据了。所以我们尝试着引入了一种对称加密方式,通信双方约定了一个随机生成的密钥。在后续的通信中,信息发送方都使用密钥对信息加密,而信息接收方通过同样的密钥对信息解密。

  但问题出现了,虽然我们在后续的通信中对明文进行了加密,但是第一次约定加密方式和密钥的通信仍然是明文。如果第一次通信就已经被拦截了,那么密钥就会泄露给中间人,中间人仍然可以解密后续所有的通信内容。

  为了解决这个问题,我们又想到了一项技术:非对称加密算法。我们可以使用非对称加密,为密钥的传输做一层额外的保护。非对称加密的一组秘钥对中,包含一个公钥和一个私钥。明文既可以用公钥加密,用私钥解密;也可以用私钥加密,用公钥解密。所以甲方首先把自己的公钥Key1发送给了乙方,乙方收到Key1后,自己生成一个用于对称加密的密钥Key2,结合Key1对Key2进行加密,然后讲加密后的Key2发送给甲方。甲方通过自己非对称加密的私钥解开公钥Key1的加密,从而获取了Key2的内容。自此甲乙就可以通过Key2进行对称加密通信了。

  似乎非对称加密的引入解决了之前的问题,中间人即使在最开始便截获了公钥Key1,他也因为没有私钥从而无法直接解密出数据。但黑客除了窃听外,还可以劫持和篡改,所以新的黑客方式出现了:在甲方发出公钥Key1后,黑客劫持并生成自己的一对公钥和私钥,并发送公钥Key3给乙方。乙方依然加密后生成新的Key2发送出来,然后继续被黑客劫持,并用其私钥解开了自己Key3的加密获得了新的Key2内容。然后就用最初劫持的Key1进行加密并发送给甲方。这样一来,后续的通信过程甲乙就依然用Key来做对称加密,而黑客已然获取了Key2来进行解密。

  到此我们就慢慢开始接近了HTTPS的设计思路,那么面对这时的方案,我们还可以如何去做改进来填补漏洞呢?首先漏洞来源于什么,来源自我们的通信不安全,能够通过技术手段来让通信无法被别人听到吗?好像近些年比较热的量子通信可以实现,但离实现还有未可预知的时间,所以我们其实是没办法保证这一点的。即我们对话的内容无法保证只有我们听到,那么就只好保证我们的对话只有我们能听懂,从而变相的实现了防窃听。既然我们双方确认暗号的过程会因为中间人的伪造而使我们无法信任对话者的身份,那么我们可以找一个有公信力可以被信任的机构来作第三方公信人啊!所以HTTPS引入了这一第三方来解决信任问题。


第二节 HTTPS介绍

  HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL(SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。),因此加密的详细内容就需要SSL。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间),提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

2.1 HTTPS通信流程

  权威的证书颁发机构(CA)即被信任的第三方,其证书至少包含如下信息:颁发机构,服务端网址,机构私钥加密服务端公钥,机构私钥加密证书签名。

证书的获取和验证、服务端客户端的通信流程如下:

  1. 服务端A,首先把自己的公钥发送给证书颁发机构,向其申请证书。
  2. 证书颁发机构自己也有一对公钥私钥。机构利用自己的私钥来加密Key1,并且通过服务端网址等信息生成一个证书签名,证书签名同样经过机构的私钥加密。证书制作完成后,机构把证书发送给了服务端A。
  3. 当客户端B向A请求通信时,客户端B会发起一个http请求,连接到服务器A的443端口。A则不再直接返回自己的公钥,而是把自己申请的数字证书返回给B。
  4. B收到证书以后,要做的第一件事情是验证证书的合法性。需要说明的是,各大浏览器和操作系统已经维护了所有权威证书机构的名称和公钥。所以B只需要知道是哪个机构颁布的证书,就可以从本地找到对应的机构公钥,解密出证书签名。接下来,B按照同样的签名规则,自己也生成一个证书签名,如果两个签名一致,说明证书是有效的(可能需要验证证书中包含的地址与正在访问的地址是否一致,证书是否过期等)。验证成功后,B就可以放心地再次利用机构公钥,解密出服务端A的公钥Key1。如果验证通过,或用户接受了不受信任的证书,浏览器就会生成一串随机字符串,让服务端用私钥加密随机字符串,返回结果后客户端再用公钥解密,相当于做一次确认工作。若与之前的随机字符串一致,则说明服务端确实是私钥的持有者。
  5. 验证完服务端身份后,客户端会生成一个对称加密的算法和对应密钥,所以B生成了自己的对称加密密钥Key2,并且用服务端公钥Key1加密Key2,发送给服务端A。此时被黑客截获也没用,因为只有服务端的私钥才可以对其进行解密。
  6. 最后,A用自己的私钥解开加密,得到对称加密密钥Key2。之后客户端与服务端可以用这个对称加密算法来加密和解密通信内容了。

  证书的签名由服务端网址等信息生成,经过机构私钥加密,黑客无法进行篡改。我们知道TCP/IP模型包括:应用层(HTTP/FTP)->传输层(TCP/UDP)->网络层(IP/ARP)->数据链路层,HTTPS就是在HTTP基础上增加了SSL安全层,上述一系列认证过程就在这一层进行,即:HTTP->SSL->TCP->IP->数据链路层。注:最新推出的TLS协议,是SSL 3.0协议的升级版,和SSL协议的大体原理是相同的。

  HTTPS涉及到加密,想要彻底的了解HTTPS,需要学习一下密码学相关的知识。

2.2 加密

  可以参考加密算法

  1. 密钥:通常是一个字符串或数字,进行加密/解密算法时使用。公钥和私钥都是密钥,只不过一般公钥是对外开放的,加密时使用;私钥是不公开的,解密时使用。
  2. 非对称加密算法(公钥加密):有RSA、DSA/DSS、Elgamal、Rabin、D-H、ECC等。在客户端与服务器相互验证的过程中用的就是非对称加密算法。RSA密码体制就是公钥密码体制,RSA的一对公钥和私钥都可以用来加密和解密。比如公钥加密后只能由私钥解密;私钥加密后只能由公钥解密。且一方加密的内容只能由对方进行解密。
  3. 对称加密算法(私钥加密):有AES、DES、3DES、TDEA、Blowfish、RC4、RC5、IDEA等。加密使用的密钥和解密使用的密钥是同一个密钥。由于加密算法是公开的,若要保证安全性,密钥不能对外公开。通常用来加密消息体。
  4. HASH算法:有MD5,SHA1,SHA256。用来确认信息没有被篡改。主要用来生成签名,签名是加在信息后面的,可以证明信息没有被修改过。一般对信息先做hash计算得到一个hash值,然后用私钥加密(这个加密一般是非对称加密)作为一个签名和信息一起发送。接收方收到信息后重新计算信息的hash值,且和信息所附带的hash值解密后进行对比。如果一样则认为没有被修改,反之则认为修改过,不做处理。可能有一种情况,黑客修改了信息并把hash值也改了,从而让他们相匹配。所以hash值一般都是加密后(生成签名)再和信息一起发送,确保hash值不会被修改。
  5. 数字证书:主要包含证书发布机构,证书有效期,公钥,证书所有者,签名使用的算法,指纹以及指纹算法。数字证书可以保证里面的公钥一定是证书持有者的。
  6. 数字签名:数字签名原理:将明文通过Hash算法加密生成摘要,再将消息体摘要用私钥加密后就是签名了。当下一级证书或客户端需要时就返回这个整体。数字签名主要作用就是配合Hash算法保证信息没有被篡改。当https验证通过后,一般会改用对称加密方式通信,因为RSA公钥体制比较耗性能。所以数字签名只存在于验证阶段。
  7. 证书链:证书是分级的,证书链由多个证书一级一级组成,拿到上级证书的公钥才能解密本级证书。只有最底层的证书是自签名的,自己颁发给自己。

2.3 HTTPS原理

2.3.1 数字证书

  HTTPS的安全性主要依赖于对数字证书的验证以及非对称加密机制,客户端具体是如何判断证书的合法性的?首先我们先看一下证书的主要内容。

数字证书内容:

  1. Issuer–证书的发布机构

发布证书的机构,指明证书是哪个公司创建的(并不是指使用证书的公司)。出了问题具体的颁发机构是要负责的

  1. Valid from,Valid to–证书的有效期

证书的使用期限。过了这个期限证书就会作废,不能使用。

  1. Public key–公钥

公钥用于对消息加密。

  1. Subject–主题

证书是颁发给谁了,一般是个人或公司名称或机构名称或公司网站的网址。

  1. Signature algorithm–签名所使用的算法

数字证书的数字签名所使用的加密算法,根据这个算法可以对指纹解密。指纹加密的结果就是数字签名。

  1. Thumbprint,Thumbprint algorithm–指纹以及指纹算法(一种HASH算法)

指纹和指纹算法会使用证书机构的私钥加密后和证书放在一起。主要用来保证证书的完整性,确保证书没有修改过。使用者在打开证书时根据指纹算法计算证书的hash值,和刚开始的值一样,则表示没有被修改过。

2.3.2 合法性验证

  那么客户端如何检测数字证书是合法的并是所要请求的公司的?

  首先应用程序要读取证书中的Issuer(发布机构),然后会在操作系统或浏览器内置的受信任的发布机构中去找该机构的证书。如果找不到程序会有相应提醒,未必信任的证书来源。如果找到发布机构,或者未找到但用户确认使用该证书,就会拿到上级证书的公钥,解密本级证书,得到数字指纹。然后对本级证书的公钥进行数字摘要算法(证书中提供的指纹加密算法)计算结果,与解密得到的指纹对比。如果一样,说明证书没有被修改过。公钥可以放心使用,可以开始握手通信了。

操作系统为什么会有证书发布机构的证书?

证书发布机构除了给别人发布证书外,自己也有自己的证书。在操作系统安装好时,受信任的证书发布机构的数字证书就已经被微软安装在操作系统中了,根据一些权威安全机构的评估,选取一些信誉很好并且通过一定安全认证的证书发布机构,把这些证书默认安装在操作系统中并设为信任的数字证书。发布机构持有与自己数字证书对应的私钥,会用这个私钥加密所有他发布的证书及指纹整体作为数字签名。

  在获得公钥后,客户端会生成随机数并用公钥加密,让服务端用私钥解密来确保对方是否真的持有私钥。但是,黑客也可以发送字符串让服务器用私钥加密,并得到加密后的信息,从而找到规律,导致私钥的安全性下降。这种情况如何解决?

  服务端并不是真的加密这个字符串,而是把字符串进行hash计算后再进行加密后发送给客户端。客户端收到后再解密这个hash值与原来字符串的hash值对比,从而确定对方是否持有私钥。

  在通信的过程中,黑客可以截获加密内容,虽不能理解具体内容,但可以捣乱,修改内容或重复发送该内容,如何解决?

  给通信的内容加版本号或随机值,如果接收到版本号或随机值不相同的信息,双方立刻停止通信。若一直捣乱就无法正常通信,因为有人控制了你的路由器,可以针对你。所以一些对于安全性较强的部门来说就不使用公网,而是内部网络,一般不会被破环通信。

2.3.2 单向验证和双向验证

  https验证过程是分为单向验证和双向验证。

单向认证:

  1. 客户端保存着服务端的证书并信任该证书即可.
  2. https一般是单向认证,这样可以让绝大部分人都可以访问你的站点。

双向认证:

  1. 先决条件是有两个或两个以上的证书,一个是服务端证书,另一个或多个是客户端证书。
  2. 服务端保存着客户端的证书并信任该证书,客户端保存着服务端的证书并信任该证书。这样,在证书验证成功的情况下即可完成请求响应。(只有服务器验证客户端证书并通过,客户端验证服务器证书并通过,才可以进行通信)
  3. 双向认证一般企业应用对接。(所以企业与企业间对接,大部分都是自签证书)

总结:单向验证只需要客户端验证服务器证书即可,双向则需要双方都验证。


第三节 实战

3.1 获取证书

3.1.1 权威机构证书

  可以通过阿里云等销售方来购买SSL证书,不过价格对于个人开发者和学生来说不是一笔小的费用,请根据自身情况酌情选购。

  可能我们已经拿到了SSL证书,可以将其直接导入密钥库,通过以下命令创建包含证书的新密钥库。

keytool -import -alias tomcat -file myCertificate.crt -keystore keystore.p12 -storepass password

3.1.2 自行申请证书

  自行申请证书获得自签名证书,其用途只是为了开发和测试,在生产环境中应该使用由权威机构(Certificate Authority,CA)颁发的证书。

  获取证书的取方式有很多,这里介绍通过Java的keytool数据证书工具生成证书的方法,Keytool是与JDK一起提供的一个证书管理实用程序,因此如果安装了JDK,就应该已经有Keytool可用了。

  第一步,生成一对加密密钥,使用它们生成SSL证书并存储到密钥库(keystore)中。keytool文档中定义密钥库是一个加密密钥、X.509证书链、可信证书的数据库。为了启用HTTPS,我们需要提供一个Spring Boot应用程序,其密钥库包含SSL证书。密钥库最常用的两种格式是JKS(Java专用格式)和PKCS12(行业标准格式)。JKS是过去的默认格式,但如今Oracle建议采用后者。

  打开命令端执行以下命令,生成JKS证书。

keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -keystore keystore.jks -validity 3650

  如果想要生成PKCS12证书,可以采用下个命令。

keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

  其中-alias是证书的别名,RSA是加密算法,-keystore后是输出证书的路径所在

  执行后分别输入密码(至少6位)和一些个人信息(可跳过),最后生成了包含SSL证书的keystore。

  我们可能想要查看keystore的内容,可以输入以下命令。

keytool -list -v -keystore keystore.jks

keytool -list -v -storetype pkcs12 -keystore keystore.p12

  如果已经有了一个JKS的密钥库,想要转换为PKCS12,可以输入以下命令。

keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype pkcs12

3.2 Spring Boot配置HTTPS

  有了证书后,如何在Spring Boot项目中配置使用呢?

  1. 将证书文件放置到项目根目录。
  2. 在配置文件application.properties中添加如下内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 自定义端口配置
server.port=8443
server.port.http=8080

# Tell Spring Security (if used) to require requests over HTTPS,自动阻止HTTP请求
# security.require-ssl=true

# HTTPS SSL 配置
server.ssl.enabled=true
# The format used for the keystore 密钥库格式
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate 指定密钥库文件路径
server.ssl.key-store=classpath:keystore.p12
# The password used to generate the certificate 证书密令
server.ssl.key-store-password=password
# The alias mapped to the certificate 证书别名
server.ssl.key-alias=tomcat
  1. 通过配置类HttpsConfig来配置HTTPS访问,重定向HTTP到HTTPS,代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class HttpsConfig {

@Value("${server.port.http}")
private int serverPortHttp;

@Value("${server.port}")
private int serverPortHttps;

@Bean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/*");
securityConstraint.addCollection(securityCollection);
context.addConstraint(securityConstraint);
}
};
factory.addAdditionalTomcatConnectors(redirectConnector());
return factory;
}

private Connector redirectConnector() {
Connector connector = new Connector(Http11NioProtocol.class.getName());
connector.setScheme("http");
connector.setPort(serverPortHttp);
connector.setSecure(false);
connector.setRedirectPort(serverPortHttps);
return connector;
}
}
  1. 启动项目,并访问对应URL:http://localhost:8080https://localhost:8443,观察结果。

3.3 分发SSL证书给客户端

  自签名的SSL证书不会被浏览器信任,并会警告不安全。通过提供证书,可以使客户端信任您的应用程序。因为您将证书存储在密钥库中,所以需要提取它。通过以下命令通过密钥库生成crt证书。

keytool -export -keystore keystore.p12 -alias tomcat -file myCertificate.crt

  有了证书后可以将其导入客户端,如果使用的是PKCS12格式的密钥库,应该可以直接使用它,而无需提取证书。如果在本地主机上部署应用程序,可能需要在浏览器上执行进一步的步骤:启用与本地主机的不安全链接。如在Chrome中通过在搜索栏编写此URL:chrome://flags/#allow-insecure-localhost 并激活relative选项。

3.3.1 在JRE密钥库中导入证书

  要让JRE信任我们的证书,需要将其导入到cacerts中(负责保存证书的JRE密钥库)。首先找到JDK路径,我们先用$JDK_HOME来表示此路径。然后通过以下命令使JRE信任证书,需要输入JRE密钥库密码(默认为changeit或changeme),然后信任此证书。如果一切顺利,我们会看到证书被添加到密钥库的提示。

keytool -importcert -file myCertificate.crt -alias tomcat -keystore $JDK_HOME/jre/lib/security/cacerts

3.4 SSL证书转换

3.4.1 密钥库生成CRT和KEY

  服务器端获得了JKS或PKCS12格式的密钥库,但对于一些如前端项目NodeJs等,需要用Crt证书和Key密钥来启用HTTPS,可以通过keytool和openssl工具来完成这一任务。

  1. 获取已有JKS证书的文件和口令。

  2. 通过keytool工具将JKS证书转换为P12格式,命令如下所示,分别输入新旧口令即可生成目标文件。

keytool -importkeystore -srckeystore D:\server.keystore -destkeystore D:\server.p12 -srcstoretype jks -deststoretype pkcs12

  1. 通过openssl工具将P12格式证书转换为CRT证书,命令如下所示,在输入口令后即生成目标文件。

openssl pkcs12 -in D:\server.p12 -nokeys -clcerts -out D:\server.crt

  1. 通过openssl工具将P12格式证书转换为非加密的key,命令如下所示,在输入口令后即生成目标文件。

openssl pkcs12 -in D:\server.p12 -nocerts -nodes -out D:\server.key

3.4.2 案例:客户只提供了CRT证书

  项目需要升级HTTPS,且我们只有云服务器的使用权,询问甲方后只拿到了CRT证书。

  首先尝试根据证书直接创建包含证书的新密钥库。

keytool -import -alias tomcat -file myCertificate.crt -keystore keystore.p12 -storepass password

  分别生成了JKS和PKCS12格式的密钥库,然后直接把密钥库配置到Spring Boot应用,启动后报错。

java.io.IOException Alias name not identifying a key entry

错误原因:

  1. 这个错误表示JVM中证书的别名与bitbucket.properties中指定的别名不匹配。默认情况下,bitbucket服务器查找别名tomcat。可以通过以下命令查看加载到密钥库中的别名列表:keytool -list -v -keystore {Path to Keystore File}
  2. 如果密钥库缺少私钥,且导入的证书是一个trustedCertEntry而不是PrivateKeyEntry或KeyEntry会导致此错误。可以通过以下指令列出证书信息。
1
2
3
4
5
keytool -list -v -keystore mykeystore.jks -alias tomcat
Enter keystore password:
Alias name: tomcat
Creation date: Jan 22, 2018
Entry type: trustedCertEntry

  通过运行命令发现客户所给证书缺少私钥。

解决方案:

  1. 通过向bitbucket.properties添加以下属性来重写或指定正确的别名:server.ssl.key-alias={Alias Name}
  2. 确保将证书导入为privatekey和certificate,而不仅仅是trustedcertificate。

  最后和客户打嘴皮子很久终于要到了私钥和密令解决了问题。


参考博客和文章书籍等:

How to enable HTTPS in a Spring Boot Java application

Unable to start Tomcat due to java.io.IOException Alias name not identifying a key entry

让面试官膜拜你的HTTPS运行流程(超详细)

漫画:什么是 HTTPS 协议?

[SSL证书转换(一)]关于JKS 转换成 CRT 和 KEY

使用openssl生成免费证书

OpenSSL官网下载地址

使用 HTTPS 的网站也能被黑客监听到数据吗?

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容