证书的生成
- TLS是传输层安全协议(Transport Layer Security)的缩写,是一种对基于网络的传输的加密协议,可以在受信任的第三方公证基础上做双方的身份认证。TLS可以用在TCP上,也可以用在无连接的UDP报文上。协议规定了身份认证、算法协商、密钥交换等的实现。
- SSL是TLS的前身,现在已不再更新
- HTTPS是在基于TLS/SSL的安全套接字上的的应用层协议,除了传输层进行了加密外,其它与常规HTTP协议基本保持一致
- 证书是TLS协议中用来对身份进行验证的机制,是一种数字签名形式的文件,包含证书拥有者的公钥及第三方的证书信息。
证书分为2类:自签名证书和CA证书。一般自签名证书不能用来进行身份认证,如果一个server端使用自签名证书,client端要么被设置为无条件信任任何证书,要么需要将自签名证书的公钥和私钥加入受信任列表。但这样一来就增加了server的私钥泄露风险。
TLS基于CA的身份认证基本原理是:首先验证方需要信任CA提供方自己的证书(CAcert),比如证书在操作系统的受信任证书列表中,或者用户通过“安装根证书”等方式将 CA的公钥和私钥加入受信任列表;然后CA对被验证方的原始证书进行签名(私钥加密),生成最终的证书;验证方得到最终的证书后,利用CAcert中包含的公钥进行解密,得到被验证方的原始证书。
根据RSA的加密原理,如果用CA的公钥解密成功,说明该证书的确是用CA的私钥加密的,可以认为被验证方是可信的。
下面我们以客户端单方对服务器端进行身份认证场景,来讲解如何做证书的生成。
证书的生成可以用linux的OpenSSL工具链。对于一个网站,首先必须有自己的私钥,私钥的生成方式为:
1 | openssl genrsa -out ssl.key 2048 |
私钥必须妥善保管,既不能丢失,也不能泄露。如果发生丢失和泄露,必须马上重新生成,以使旧的证书失效。
如果要对私钥进行传输/备份,建议先对私钥进行密码加密:
1 | openssl rsa -in ssl.key -des3 -out encrypted.key |
利用私钥就可以生成证书了。OpenSSL使用x509命令生成证书。这里需要区分两个概念:证书(certificate)和证书请求(certificate sign request)
- 证书是自签名或CA签名过的凭据,用来进行身份认证
- 证书请求是对签名的请求,需要使用私钥进行签名
x509命令可以将证书和证书请求相互转换,不过我们这里只用到从证书请求到证书的过程
从私钥或已加密的私钥均可以生成证书请求。生成证书请求的方法为:
1 | openssl req -new -key ssl.key -out ssl.csr |
如果私钥已加密,需要输入密码。req命令会通过命令行要求用户输入国家、地区、组织等信息,这些信息会附加在证书中展示给连接方。
接下来用私钥对证书请求进行签名。根据不同的场景,签名的方式也略有不同
自签名,生成私有证书
自签名的原理是用私钥对该私钥生成的证书请求进行签名,生成证书文件。该证书的签发者就是自己,所以验证方必须有该证书的私钥才能对签发信息进行验证,所以要么验证方信任一切证书,面临冒名顶替的风险,要么被验证方的私钥(或加密过的私钥)需要发送到验证方手中,面临私钥泄露的风险。
当然自签名也不是一无用处,比如需要内部通讯的两台电脑需要使用加密又不想用第三方证书,可以在两端使用相同的私钥和证书进行验证(当然这样就跟对称加密没有区别了)
自签名的方法为:
1 | openssl x509 -req -in ssl.csr -signkey ssl.key -out ssl.crt |
同样如果ssl.key已加密,需要输入密码。
自签名,生成CA证书
CA证书是一种特殊的自签名证书,可以用来对其它证书进行签名。这样当验证方选择信任了CA证书,被签名的其它证书就被信任了。在验证方进行验证时,CA证书来自操作系统的信任证书库,或者指定的证书列表。
生成自签名证书的方法为:
1 | openssl x509 -req -in sign.csr -extensions v3_ca -signkey sign.key -out sign.crt |
利用CA证书进行签名
使用CA证书对其它证书进行签名的方法为:
1 | openssl x509 -req -in ssl.csr -extensions v3_usr -CA sign.crt -CAkey sign.key -CAcreateserial -out ssl.crt |
花钱购买证书机构的签名
利用上述方法,受信任的机构就可以用自己的私钥(sign.key)对其他人的证书进行签名。我们看到,只需要把证书请求(ssl.csr)发给证书机构,证书机构就可以生成出签名过的证书(ssl.crt)。目前购买证书签名服务的价格大约为100-400元/年。
可选的证书机构有:godaddy startssl namecheap
证书/钥格式
openssl默认使用PEM格式(形如—–BEGIN CERTIFICATE—– … … —–END CERTIFICATE—)存放证书和私钥,nginx/node.js可以使用该格式启动服务。
但使用tomcat或java客户端/android时,不能使用该格式的证书。jdk中包含了一个keytool命令,可以完成整个的证书生成过程,不过这里我们只用openssl工具也可以完成java的支持,即使用PKCS12格式对证书进行打包。
PKCS12格式文件,可以包含多个证书/私钥对,指定多个受信任的server(也可以不包含证书),每个server有一个alias name。我们来看最简单的只包含一个alias的文件生成:
1 | openssl pkcs12 -export -in sign.crt -inkey sign.key -out sign.p12 |
上面的命令将CA证书及其私钥导出为sign.p12文件,导出时需要指定一个密码。
java客户端程序中对p12文件进行信任
如果我们的服务器使用了自己创建的CA证书签名的证书,或者使用了自签名证书,客户端在使用ssl时,需要将导出的证书文件加入到信任列表中。下面是一段导入证书的示例代码:
1 | 1 javax.net.SocketFactory initSSLSocketFactory(InputStream keyin, char[] password) throws Exception { |
我们得到了一个SocketFactory,可以用它来初始化ssl/https连接。
TLS 证书生成方法
生成私钥 ssl.key:
1 openssl genrsa -out ssl.key 2048如果要对私钥进行传输/备份,建议先对私钥进行密码加密:
1 openssl rsa -in ssl.key -des3 -out encrypted.key利用私钥就可以生成证书了。OpenSSL使用x509命令生成证书。这里需要区分两个概念:证书(certificate)和证书请求(certificate sign request)
- 证书是自签名或CA签名过的凭据,用来进行身份认证
- 证书请求是对签名的请求,需要使用私钥进行签名
x509命令可以将证书和证书请求相互转换,不过我们这里只用到从证书请求到证书的过程
从私钥或已加密的私钥均可以生成证书请求。生成证书请求 ssl.csr 的方法为:
1 openssl req -new -key ssl.key -out ssl.csr如果私钥已加密,需要输入密码。req命令会通过命令行要求用户输入国家、地区、组织等信息(也可以直接指定命令参数提供),这些信息会附加在证书中展示给连接方。
接下来用私钥对证书请求进行签名。根据不同的场景,签名的方式也略有不同,一般有两种
自签
自签名的原理是用私钥对该私钥生成的证书请求进行签名,生成证书文件。该证书的签发者就是自己,所以验证方必须有该证书的私钥才能对签发信息进行验证,所以要么验证方信任一切证书,面临冒名顶替的风险,要么被验证方的私钥(或加密过的私钥)需要发送到验证方手中,面临私钥泄露的风险。
当然自签名也不是一无用处,比如需要内部通讯的两台电脑需要使用加密又不想用第三方证书,可以在两端使用相同的私钥和证书进行验证(当然这样就跟对称加密没有区别了)
自签名的方法为:
1 openssl x509 -req -in ssl.csr -signkey ssl.key -out ssl.crt同样如果ssl.key已加密,需要输入密码。
- 配置简单的 nginx tls 证书可以使用这个方式
CA 签
CA证书是一种特殊的自签名证书,可以用来对其它证书进行签名。这样当验证方选择信任了CA证书,被签名的其它证书就被信任了。在验证方进行验证时,CA证书来自操作系统的信任证书库,或者指定的证书列表。K8S 集群RBAC权限认证就使用这种模式。
生成自签名证书的方法为:
1 openssl x509 -req -in sign.csr -extensions v3_ca -signkey sign.key -out sign.crt使用CA证书对其它证书进行签名的方法为:
1 openssl x509 -req -in ssl.csr -extensions v3_usr -CA sign.crt -CAkey sign.key -CAcreateserial -out ssl.crt
1. HTTP 的问题
HTTP 协议是超文本传输协议(Hyper Text Transfer Protocol)的缩写,它是从 WEB 服务器传输超文本标记语言 HTML 到本地浏览器的传送协议。HTTP 设计之初是为了提供一种发布和接收 HTML 页面的方法,时至今日,它的作用已经不仅仅于此了。
对于我们 Java 工程师而言,HTTP 应该算是再熟悉不过的东西了,目前 HTTP 有多个版本,使用较多的是 HTTP/1.1 版本。
然而 HTTP 协议有一个缺陷那就是它是通过明文传输数据的,用户通过 HTTP 协议传输的内容很容易被恶意拦截,并且黑客可以伪装成服务端,向用户传送错误的信息,并且能轻易获取用户的隐私信息,而这些操作用户是完全无感知的。
由于存在这样的安全隐患,现在小伙伴们见到的大部分网站都在逐步转为 HTTPS,HTTP 网站会越来越少了。
2. HTTPS
HTTPS(HyperText Transfer Protocol Secure)中文译作超文本传输安全协议,这是一种通过计算机网络进行安全通讯的传输协议。
HTTPS 本质上还是由 HTTP 进行通信,只是在 HTTP 协议和 TCP 层之间增加了一个 SSL 的安全传输协议。整个传输的加密过程都在新的安全层 SSL/TLS 中实现,而原来的 HTTP 层的传输流程保持不变,这样就很好地兼容了旧的 HTTP 协议,也沿袭了 TCP/IP 协议族的分层思想。
通过 HTTPS,客户端可以确认服务端的身份,保证数据在传输过程中不被篡改,当我们在自己的浏览器上与某一个网站建立 HTTPS 连接的时候,满足如下情况可以表示这个服务端可以被信任:
- 首先我们的操作系统中安装了正确且受信任的证书。我们在 cmd 命令行中执行
certmgr.msc命令,可以查看操作系统已经安装的证书列表。
- 浏览器本身正确实现了 HTTPS。
- 被访问的网站提供了一个证书,这个证书是由一个操作系统所信任的证书颁发机构签发的,操作系统所信任的证书颁发机构一般都预装在操作系统中,通过第一步的方式可以查看。
- 被访问的网站所提供的证书被成功认证。
这里边涉及到一些证书和协议的概念,接下来松哥和大家把整个过程捋一捋。
3. TLS/SSL
前面我们提到,HTTPS 就是在 HTTP 的基础之上增加了 TLS/SSL,那么这两个东西该如何理解呢?
SSL/TLS 是一种密码通信方案,算是目前使用最广泛的密码通信方案了。SSL 全称是 Secure Socket Layer,中文译作安全套接层,是 1994 年由 Netscape 公司设计的一套协议,并与 1995 年发布了 3.0 版本;TLS 全称是 Transport Layer Security,中文译作传输层安全,则是 IETF 在 SSL3.0 基础上设计的协议,实际上相当于 SSL 的后续版本,目前 TLS 先后迭代了
TLS 1.0、TLS 1.1、TLS 1.2和TLS 1.3,目前被广泛使用的是TLS 1.2版本。SSL/TLS 涉及到了密码学中的对称加密、非对称加密、数字签名等等,算是密码学领域里的集大成者了。
3.1 TLS
接下来我们就来看看 TLS 如何确保 HTTP 安全。
为了确保客户端和服务端之间的数据安全,我们很容易想到一种方案就是对传输的数据进行加密,没错,这是一个办法,事实上也是这么做的。
加密又分为两种:
- 对称加密
- 非对称加密
那么该使用哪一种呢?
对称加密,也就是加密密钥和解密密钥是同一个,当浏览器和服务端需要进行通信的时候,约定好一个密钥,然后使用这个密钥对发送的消息进行加密,对方收到消息之后再使用相同的密钥对消息进行解密。但是,在 B/S 架构的项目中,这种方案显然不合适,一个网站把自己的密钥告诉全世界所有的浏览器,那加密和不加密还有区别吗?
有小伙伴可能又想到了不对称加密,不对称加密倒是个办法,因为不对称加密是有一个密钥对公钥和私钥,公钥可以公布出来告诉所有人,私钥只有自己知道。通信的时候,客户端首先使用公钥对消息进行加密,服务端收到之后,再通过私钥对消息进行解密,这看起来似乎挺完美的。但是!!!非对称加密存在一个问题,就是非对称加密和解密相当耗时,通过这种方式处理加解密效率太低。
那怎么办?我们可以将两者结合起来。
具体来说,就是这样:首先服务端会生成一个非对称加密的密钥对,私钥自己保存,公钥发送给客户端,客户端拿到这个公钥之后,再生成一个对称加密的密钥,然后把对称加密的密钥通过公钥进行加密,加密之后发送给服务端,服务端通过私钥进行解密,这样客户端和服务端就可以通过对称加密进行通信了。
事实上,TLS 大致上的思路就是这样的。
不过上面这个方案还是有一个漏洞,那就是服务端要通过明文传输的方式把公钥发送给客户端,这个过程还是不安全的,可能被人恶意截胡,那么这个问题该如何解决呢?
这就涉及到另外一个概念叫做数字证书了。
3.2 CA
数字证书是一个包含了目标网站各种信息如网站域名、证书有效期、签发机构、用于生成对称密钥的公钥、上级证书签发的签名等的文件,通过数字证书我们可以确认一个用户或者服务站点的身份。
实际场景中的数字证书是一系列的,形成了一个信任链,信任链的最顶端是 CA。
CA 是 Certificate Authority 的简写,它是一个负责发放和管理数字的证书的第三方权威机构。CA 的工作流程是这样的:
- CA 自己给自己颁发的用自己的私钥签名的证书称为根证书,根证书的私钥安全性至关重要,根证书的私钥都是被保存在离线计算机中,有严格的操作规章,每次需要使用时,会有专人将数据通过 USB 拷贝过去,操作完了以后,再将数据带出来(这个专指 CA 根证书的私钥)。
- 一个用户想要获取一个证书,首先自己得有一个密钥对,私钥自己留着,公钥以及其他信息发送给 CA,向 CA 提出申请,CA 判明用户的身份之后,会将这个公钥和用户的身份信息绑定,并且为绑定后的信息进行签名(签名是通过 CA 根证书的私钥进行的),最后将签名后的证书发给申请者。
- 一个用户想要鉴定一个证书的真伪,就通过 CA 的公钥对证书上的数字签名进行验证,验证通过,就认为这个这个证书是有效的。
上面这个流程中有一个重要前提,那就是 CA 受到大家所有人的信任。
然而在实际操作中,我们并不能直接去跟 CA 申请一个数字证书,因为全世界要认证的内容太多了,CA 搞不过来,而且频繁的找 CA 申请,还有可能导致私钥泄漏,这可就是一个大的灾难了。
那怎么办呢?实际操作中,我们可以基于 CA 来构建一个信任链。具体来说,步骤是这样:
- 首先我们的手机、笔记本等操作系统中都预装了 CA 颁发的根证书,他们是所有信任构建的基石,前面松哥已经截图给大家看了 Windows 中预装的根证书了。
- 假设 CA 签发了一个证书 A,在这个过程中 CA 称为 Issuer,A 称为 Subject,假设 A 是一个受信任的中间证书,已经预装在我们的操作系统中了。现在由 A 利用它自己的私钥给某一个网站签发了一个证书 B。
- 现在当我们的电脑需要访问该网站的时候,该网站就会给我们发来一个证书 B,由于我们的浏览器并不知道 B 证书是否合法,但是我们的电脑上已经预装了 A 证书,我们可以从 A 证书中提取出 A 的公钥,然后利用 A 的公钥对 B 证书的签名进行验证(因为 B 证书的签名是 A 的私钥签的),如果验证通过了,就说明 B 是合法的。
- 相同的道理,B 也可以继续签发 C 证书,C 继续签发 D 证书,这样就形成了一个信任链。
- 如果服务端的签名是 D 证书,那么一般来说,服务器返回给浏览器的就会包含 B、C、D 三个证书(因为 A 证书已经在我们的电脑上了),即使只返回 D 证书,浏览器也可以根据 D 书中的信息,自动下载到 B、C 两个证书然后进行验证。
总结一下:
- CA 是一个权威的机构,是一个发证机关,CA 发出来的证书可以证明一个人或者组织的身份。
- 任何人都可以得到 CA 的证书(含公钥),用以验证 CA 所签发的证书。
- 每一个数字证书都是由上级证书的私钥来签发的,处于最顶层的就是 CA 签发的根证书了,这个根证书没有上级证书了,所以这个根证书实际上是由 CA 自己的私钥来签发的,这也叫做自签名,即 Self-Signed。
当我们有了数字签名之后,就可以解决 3.1 小节最后提出的问题了。服务端将数字签名发给浏览器,浏览器利用系统已经内置的公钥验签,确认签名没问题,然后就提取出来数字签名中的公钥,开始协商对称加密的私钥了






