TLS和SSL

1.1 TLS/SSL基本概念

SSL(Secure Socket Layer 安全套接层)是基于HTTPS下的一个协议加密层,起初是因为HTTP在传输数据时使用的是明文,是不安全的,为了解决这一隐患网景公司(Netscape)推出了SSL安全套接字协议层,SSL是基于HTTP标准并对TCP传输数据时进行加密,在HTTP和TCP之间,所以HPPTS是HTTP+SSL/TCP的简称。

TLS(Transport Layer Security)是传输层安全性协议,是IETF把SSL经过标准化的传输协议,可以看作是SSL的升级版,目的是保障互联网通信提供安全性和数据完整性。事实上我们现在用的都是TLS,但因为历史上习惯了SSL这个称呼。

目前应用最广泛的是TLS 1.0,但是主流浏览器都已经实现了TLS 1.2的支持。TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。

SSL/TLS有单向认证和双向认证两种方式:

  • 单向认证指的是只有一个对象校验对端的证书合法性,通常都是client来校验服务器的合法性,那么client需要一个ca.crt,服务器需要server.crt、server.key。
  • 双向认证指的是相互校验,服务器需要校验每个client,client也需要校验服务器。server需要server.key 、server.crt 、ca.crt文件;client也需要client.key 、client.crt 、ca.crt文件。


1.2 TLS/SSL握手通信机制

TLS/SSL协议的基本过程:

  • 客户端向服务器端索要并验证公钥。
  • 双方协商生成”对话密钥”。
  • 双方采用”对话密钥”进行加密通信。

客户端和服务器端在正式通信之前经过握手阶段,”握手阶段”涉及四次通信,如下图所示:

"tls握手"

“握手阶段”的所有通信都是明文的,握手完成之后是通信内容是经过秘钥加密的,握手过程说明如下:


(1) 客户端发出请求(ClientHello)

首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求,在这一步客户端主要向服务器提供以下信息。

  • 支持的协议版本,比如TLS 1.0版。
  • 一个客户端生成的随机数,稍后用于生成”对话密钥”。
  • 支持的加密方法,比如RSA公钥加密。
  • 支持的压缩方法。

这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应该向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。对于虚拟主机的用户来说,这当然很不方便。2006年,TLS协议加入了一个Server Name Indication扩展,允许客户端向服务器提供它所请求的域名。


(2) 服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello,服务器的回应包含以下内容:

  • 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
  • 一个服务器生成的随机数,稍后用于生成”对话密钥”。
  • 确认使用的加密方法,比如RSA公钥加密。
  • 服务器证书。

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供”客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。


(3) 客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息:

  • 一个随机数。该随机数用服务器公钥加密,防止被窃听。
  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称”pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把”会话密钥”。

注:如果前一步服务器要求客户端证书,客户端会在这一步发送证书及相关信息。


(4) 服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的”会话密钥”,向客户端最后发送下面信息:

  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

整个握手阶段全部结束,接下来客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容。


1.3 HTTPS

HTTPS(Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP和TCP之间加入TLS/SSL安全通信协议。

HTTPS和HTTP的区别:

  • https协议需要到CA申请证书。
  • http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。
  • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • http的连接很简单,是无状态的;HTTPS协议是由TLS/SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。



2 加密解密常用术语

对于一份数据,通过一种算法,基于传入的密钥(一串由数字或字符组成的字符串,也称”key”),将明文数据转换成了不可阅读的密文,这是”加密”,同样的,密文到达目的地后,需要再以相应的算法,配合一个密钥,将密文再解密成明文,这就是”解密”。


2.1 对称加密

如果加密和解密使用的是同一个密钥,那么这就是”对称密钥加解密”,最常见的对称加密算法是DES。


2.2 非对称加解密

如果加密和解密使用的是两个不同的密钥,那么这就是”非对称密钥加解密”,最常用的非对称加密算法是RSA。这两个不同的密钥一个叫作公开密钥(publickey)另一个叫私有密钥(privatekey),公开密钥对外公开,而私有密钥则由自己保存,其实公钥和私钥并没有什么不同之处,公钥之所以成为公钥是因为它会被公开出来,产生任意份拷贝,供任何人获取,而只有服务主机持有唯一的一份私钥,这种分发模式实际上是Web站点多客户端(浏览器)与单一服务器的网络拓扑所决定的,多客户端意味着密钥能被复制和公开获取,单一服务器意味着密钥被严格控制,只能由本服务器持有,这实际上也是后面要提到的之所以能通过数据证书确定信任主机的重要原因之一。如果我们跳出web站点的拓扑环境,其实就没有什么公钥与私钥之分了,比如那些使用以密钥为身份认证的SSH主机,往往是为每一个用户单独生成一个私钥分发给他们自己保存,SSH主机会保存一份公钥,公钥私钥各有一份,都不会公开传播。

注:用公钥加密的数据,只能用私钥解密,公钥是无法解密的,同样用私钥加密的数据也只能用公钥来解密。


2.3 数字摘要

在下载文件的时候经常会看到有的下载站点也提供下载文件的”数字摘要”,供下载者验证下载后的文件是否完整,或者说是否和服务器上的文件”一模一样”。其实,数字摘要就是采用单项Hash函数将需要加密的明文”摘要”成一串固定长度(128位)的密文,这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,同样的明文其摘要必定一致。 因此,”数字摘要”叫”数字指纹”可能会更贴切一些。”数字摘要”是https能确保数据完整性和防篡改的根本原因。


2.4 数字签名

数字签名是水到渠成的技术,有了”非对称密钥加解密”和”数字摘要”两项技术之后,我们能做些什么呢?假如发送方想把一份报文发送给接收方,在发送报文前,发送方用一个哈希函数从报文文本中生成报文摘要,然后用自己的私人密钥对这个摘要进行加密,这个加密后的摘要将作为报文的”签名”和报文一起发送给接收方,接收方首先用与发送方一样的哈希函数从接收到的原始报文中计算出报文摘要,接着再用发送方的公用密钥来对报文附加的数字签名进行解密,如果这两个摘要相同、那么接收方就能确认报文是从发送方发送且没有被遗漏和修改过,这就是结合”非对称密钥加解密”和”数字摘要”技术所能做的事情,这也就是人们所说的”数字签名”技术。在这个过程中,对传送数据生成摘要并使用私钥进行加密的过程就是生成”数字签名”的过程,经过加密的数字摘要,就是人们所说的”数字签名”。

数字签名技术就是对”非对称密钥加解密”和”数字摘要”两项技术的应用,它将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。数字签名只能验证数据的完整性,数据本身是否加密不属于数字签名的控制范围。

综上所述,数字签名有两种功效:一是能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名。二是数字签名能确定消息的完整性。


2.5 数字证书

数字证书是值得信赖的公钥,只从”准确认证发送方身份”和”确保数据完整性”两个安全方面来看,数字签名似乎已经完全做到了,还有漏洞握手过程中,用户收到的公钥是否真实可靠。传输过程会出现第三方劫持替换公钥,并串改信息可能。为了解决公钥是真实可靠问题,需要有一个权威的值得信赖的第三方机构(一般是由政府审核并授权的机构)来统一对外发放公钥,只有公钥在权威机构通过认证的,说明证书是真实可靠的。这种机构被称为证书权威机构(Certificate Authority,简称CA),它们所发放的包含主机机构名称、公钥在内的文件就是人们所说的”数字证书”,数字证书包含内容:

  • 证书颁发机构的名称
  • 证书本身的数字签名
  • 证书持有者公钥
  • 证书签名用到的Hash算法

数字证书的颁发过程:用户首先产生自己的密钥对,并将公共密钥及部分个人身份信息传送给认证中心。认证中心在核实身份后,将执行一些必要的步骤,以确信请求确实由用户发送而来,然后,认证中心将发给用户一个数字证书,该证书内包含用户的个人信息和公钥信息,同时还附有认证中心的签名信息,用户拿到证书之后就可以进行相关的安全通信。数字证书各不相同,每种证书可提供不同级别的可信度。可以从证书发行机构获得您自己的数字证书。


浏览器默认都会内置CA根证书,其中根证书包含了CA的公钥,验证证书的有效性:

  • 如果证书颁发的机构是伪造的,浏览器不认识,直接认为是危险证书。
  • 如果证书颁发的机构是确实存在,浏览器会根据CA名,找到对应内置的CA根证书、CA的公钥。用CA的公钥,对伪造的证书的摘要进行解密,发现解不了,认为是危险证书。
  • 对于篡改的证书验证,使用CA的公钥对数字签名进行解密得到摘要A,然后再根据签名的Hash算法计算出证书的摘要B,对比A与B,若相等则正常,若不相等则是被篡改过的。
  • 证书可在其过期前被吊销,通常情况是该证书的私钥已经失密。较新的浏览器如Chrome、Firefox、Opera和Internet Explorer都实现了在线证书状态协议(OCSP)以排除这种情形:浏览器将网站提供的证书的序列号通过OCSP发送给证书颁发机构,后者会告诉浏览器证书是否还是有效的。



3 使用cfssl生成自签名证书

cfssl是用于生成颁发TLS/SSL证书的开源工具,cfssl不仅是分发证书的工具,也是证书颁发机构(CA),对于使用HTTPS建立网站的任何人(从网站所有者到大型软件即服务公司)都是有用的。

3.1 安装

根据系统环境,从github(https://github.com/cloudflare/cfssl/releases)下载对应版本,然后把可执行文件添加到环境变量中。


3.2 生成自签名证书

(1) 生成根CA证书和私钥

创建根CA证书和私钥的CSR(证书签名请求文件)配置文件ca-csr.json,内容如下:

{
    "CN": "myPlatform",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "China",
            "ST": "Beijing",
            "L": "Beijing",
            "O": "OName",
            "OU": "OUName"
        }
    ]
}

配置文件字段说明:

  • CN:机构名称Comman Name,浏览器使用该字段验证网站是否合法
  • C: Country, 国家
  • ST: State,州,省
  • L: Locality,地区,城市
  • O: Organization Name,组织名称,公司名称
  • OU: Organization Unit Name,公司部门


初始化CA,会生成3个文件,分别是ca.pem(ca证书文件)、ca-key.pem(ca私钥文件)、ca.csr(证书签名请求文件),这些文件用于交叉签名或重新签

cfssl gencert -initca config/ca-csr.json | cfssljson -bare ca -


(2) 颁发本地证书和私钥

创建证书签名请求配置文件req-csr.json,内容如下:

{
    "CN": "myDomain",
    "hosts": [
        "localhost",
        "127.0.0.1",
        "domain_or_ip"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "China",
            "ST": "GuangDong",
            "L": "GuangZhou",
            "O": "myOName",
            "OU": "myOUName"
        }
    ]
}

配置文件字段说明:

  • hosts: 证书派发给哪些主机地址,ip或域名
  • CN:机构名称Comman Name,也可以是域名
  • C: Country, 国家
  • ST: State,州,省
  • L: Locality,地区,城市
  • O: Organization Name,组织名称,公司名称
  • OU: Organization Unit Name,公司部门


生成证书策略配置文件ca-config.json,让CA软件知道颁发什么样的证书,内容如下:

{
    "signing":{
        "default":{
            "expiry":"43800h"
        },
        "profiles":{
            "server":{
                "expiry":"43800h",
                "usages":[
                    "signing",
                    "key encipherment",
                    "server auth"
                ]
            },
            "client":{
                "expiry":"43800h",
                "usages":[
                    "signing",
                ]
            },
            "peer":{
                "expiry":"43800h",
                "usages":[
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            }
        }
    }
}

配置说明:

  • 有一个默认的配置default,或者根据需求设置profile下多种策略
  • signing: 表示证书可用于签名其它证书,生成的ca.pem证书中CA=TRUE
  • key encipherment: 表示证书可用于加密
  • server auth: 表示client可以用该CA对server提供的证书进行验证
  • client auth: 表示server可以用该CA对client提供的证书进行验证


根据需求颁发不同类型的证书

# 颁发服务端使用的证书,例如网站服务器
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config/ca-config.json -profile=server config/req-csr.json | cfssljson -bare server

# 颁发双方都要身份验证的证书,例如etcd集群
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config/ca-config.json -profile=peer config/req-csr.json | cfssljson -bare peer

# 颁发客户端证书
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config/ca-config.json -profile=client config/req-csr.json | cfssljson -bare client

注:-profile参数值必须是配置ca-config.json的profiles下对应字段,如果配置文件里没有www字段,颁发证书时使用-profile=www,会报错{“code”:5100,“message”:“Invalid policy: no key usage available”}


查看证书信息

# 查看cert(证书信息):
cfssl certinfo -cert myCertName.pem | jq
# 查看csr(证书签名请求)信息:
cfssl certinfo -csr myCertName.csr

# 也可以使用openssl命令查看证书和私钥信息
openssl x509 -text -noout  -in myCertName.pem
openssl rsa -text -noout -in ca-key.pem
#openssl req -text -noout -in ca.csr

# 验证派发的证书是否可用
openssl verify -CAfile ca.pem myCertName.pem



4 不同格式证书之间转换

一般证书有三种格式:

  • PEM(.pem) 前面命令生成的都是这种格式
  • DER(.cer .der) Windows 上常见
  • PKCS#12文件(.pfx .p12) Mac上常见
# PEM转换为CRT
openssl x509 -outform der -in ca.pem -out ca.crt

# PEM转换为DER
openssl x509 -outform der -in ca.pem -out ca.der

# DER转换为PEM
openssl x509 -inform der -in myserver.cer -out myserver.pem

# PEM转换为PKCS
openssl pkcs12 -export -out myserver.pfx -inkey myserver.key -in myserver.crt -certfile ca.crt

# PKCS转换为PEM
openssl pkcs12 -in myserver.pfx -out myserver2.pem -nodes



5 生成TLS/SSL证书在nginx使用示例

此示例在linux环境下运行,把新生成的证书文件路径设置到nginx配置文件,使用docker启动nginx来测试。

5.1 创建生成证书的脚本文件

脚本文件gen-server-cert.sh内容如下:

#!/bin/bash


# -------------------------------- 参数判断 --------------------------------------------
params=$@

if [ $# -lt 1 ]; then
    echo "param is empty"
    echo "usage:"
    echo "  \"$0 zhuyasen.com\" or \"$0 192.168.3.100\""
    exit
fi

# 用参数替换req-csr.json固定字段值domains_or_ips
hostFields=''
for val in $params; do
    hostFields=${hostFields}\\\"$val\\\",
done
# 去掉最后一个逗号
hostFields=${hostFields%?}


# ---------------------------------- 创建认证中心(CA) ----------------------------------

# 创建存储证书目录和配置目录
mkdir -p certs
cd certs
mkdir -p config

# 创建根CA证书和私钥的CSR(证书签名请求文件)配置文件ca-csr.json
cat > config/ca-csr.json <<EOF
{
    "CN": "myMechanism",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "Beijing",
            "L": "Beijing",
            "O": "my organization",
            "OU": "my organization unit name"
        }
    ]
}
EOF

# 生成CA证书和私钥,先判断ca-key.pem是否存在,如果存在则使用已存在的
if [[ ! -f "ca-key.pem" ]]; then
  echo "generate new ca.pem and ca-key.pem"
  cfssl gencert -initca config/ca-csr.json | cfssljson -bare ca -
else
  echo "use existing ca-key.pem."
fi


# ---------------------------------- 派发证书  ----------------------------------

# 创建证书签名请求配置文件req-csr.json,这里填写申请组织信息、ip或域名
cat > config/req-csr.json <<EOF
{
    "CN": "your-test-domain.com",
    "hosts": [
        "localhost",
        "127.0.0.1",
        "domains_or_ips"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "GuangDong",
            "L": "GuangZhou",
            "O": "communication",
            "OU": "cluster"
        }
    ]
}
EOF


# 配置证书生成策略,让CA知道颁发什么样的证书。
cat > config/ca-config.json <<EOF
{
    "signing":{
        "default":{
            "expiry":"87600h"
        },
        "profiles":{
            "server":{
                "expiry":"8760h",
                "usages":[
                    "signing",
                    "key encipherment",
                    "server auth"
                ]
            },
            "client":{
                "expiry":"8760h",
                "usages":[
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            },
            "peer":{
                "expiry":"8760h",
                "usages":[
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            }
        }
    }
}
EOF


tagName='\"domains_or_ips\"'
# 把domains_or_ips替换为输入的参数
sed -i "s/$tagName/$hostFields/g" config/req-csr.json

# 使用现有证书和私钥重新颁发新的证书,类型为服务端使用
for name in $params; do
  cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config/ca-config.json -profile=server config/req-csr.json | cfssljson -bare $name
done

# 删除多余文件
ls | grep -v pem | xargs -i rm -rf {}


5.2 创建nginx默认配置文件

nginx配置文件default.conf内容如下:

server {
    server_name  localhost;

    # 证书
    listen 443 ssl;
    ssl_certificate /etc/nginx/certs/nginx.pem;
    ssl_certificate_key /etc/nginx/certs/nginx-key.pem;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;


    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

# 访问80端口是,重定向到443端口,重定地址是nginx所在宿主机的ip或域名
server{
    listen 80;
    server_name localhost;
    rewrite ^(.*)$  https://192.168.3.5 permanent;
}


5.3 创建启动nginx的docker启动脚本文件

启动nginx脚本文件docker-compose.yml内容如下:

version: '3.1'
services:
  nginx:
    restart: always
    image: nginx
    container_name: nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - $PWD/conf.d:/etc/nginx/conf.d
      - $PWD/certs:/etc/nginx/certs
      #- $PWD/log:/var/log/nginx


上面准备的脚本文件目录:

.
├── conf.d
│   └── default.conf
├── docker-compose.yml
└── gen-server-cert.sh


5.4 使用nginx检验证书是否可以使用

# 生成本地证书
./gen-server-cert.sh nginx

# 启动nginx
docker-compose up -d

# 在chrome浏览器打开 https://192.168.3.100,点击高级,继续前往,如果能访问,说明证书正常
# 把ca.pem改名为ca.crt,然后导入浏览器的受信任的根证书颁发机构,访问就不会警告了



参考:



专题「工具」的其它文章 »