Skip to content

消息摘要、加解密、签名、证书

一、消息摘要

消息摘要,英文翻译是 message digest。

一段信息,经过摘要算法得到一串哈希值,就是摘要(digest),常见的摘要算法有 MD5、SHA1、SHA256、SHA512,以及咱们国产的 SM3 等。

摘要算法,是把任意长度的信息,映射成一个定长的字符串,两个不同的信息,是有可能算出同一个摘要值的(即所谓的“碰撞”,但概率非常小),但相同的一个信息,不管计算多少次,算出的摘要值则都是一样的。

摘要算法与加密算法不同,不存在解密的过程,是单向、不可逆的。摘要算法不用于数据的保密,而是用于数据的完整性(integrity)校验,意思是只要原信息发生任何一丁点的变化,生成出来的摘要信息都不一样。所以只要摘要没对上,就等于是原信息被篡改了,已经不完整了。

二、加解密

1、对称加密

对称加密意思是通信双方共同拥有一把密钥,优点是速度快,缺点是不安全,一旦密钥泄露了,数据就是裸奔的,常见的对称加密算法有 AES、DES、RC4,以及咱们国产的商密 SM1、SM4,其中最常用的是 AES。

2、非对称加密

非对称加密意思是通信双方持有不同的密钥,可以简单地理解为服务端的密钥是私钥(private key),客户端的密钥是公钥 (public key),二者的区别是——

  1. 私钥应仅在服务端保存,绝不可泄露。而公钥可以存在于任何的客户端,即使黑客拿到了也没有关系;

  2. 公钥加密的密文只有相对应的私钥才能解密;

  3. 私钥加密的内容,所有与之相对应的公钥都能解密;

  4. 私钥通常用来生成签名,公钥用来验证签名;

  5. 公钥和私钥是相对的,两者本身并没有规定哪一个必须是公钥或私钥。这意味着,公钥只要不对外公开,那就可以作为私钥,私钥公开后也可以作为公钥。

典型的非对称加密算法有 RSA、DSA、ECC,以及咱们国产的 SM2、SM9 等,其中最常用的是 RSA。

非对称加密的优点,就是安全系数特别高,缺点就是速度会慢一些,性能差一点。

3、对称加密与非对称加密结合

当客户端收到的服务端公钥是正确的时候,通信就是安全的,因为用正确公钥加密过的密文,只有服务端的私钥能解。那么如何保证,客户端收到的是正确的公钥呢?答案是通过非对称加密来协商发送对称加密的密钥,服务端一旦把正确的公钥安全地送达到客户端后,后续的通信,为了保证高效通信,通讯双方将会采用对称加密来加密数据。

三、数字签名

上面提到了用非对称加密来协商对称加密的密钥,就会涉及到 CA 这个话题。

首先我们先厘清一下概念,通常意义上我们说的 CA,指的是那个颁发证书的实体机构,而有些场景下我们又会把 CA 和证书划上等号,大家需要理解下这两个概念的区别。

那为什么要用到 CA 呢?因为 CA 足够权威,足够有公信力,由权威 CA 机构背书的证书咱们都认可,都默认使用权威 CA 证书的网站没有信任方面的问题。

如果你自己生成一对公钥和私钥,用你的私钥对服务端的 csr 文件(的摘要)进行签名(可以理解成加密,这种签名也叫数字签名),客户端再用你的公钥对你的签名进行验签(可以理解成解密),得到摘要。然后客户端再计算证书明文信息部分的摘要,发现和它解密得到的摘要是一样的,那理论上来说这个服务端证书是没问题的。但是你自己生成的这一对密钥,如何让客户端(比如浏览器)信服呢?凭什么让浏览器相信你呢?你的公钥是通过什么机制分发给客户端的呢?

前几年咱们的 12306 网站就是这样,自己给自己的服务端证书进行签名(即所谓的“自签名”),浏览器直接在地址栏里最前面打了一个大红叉,提示证书不可信任,并禁止了对网站的访问,说明自签名证书这条路是行不通的,只能到权威的 CA 机构那里购买证书。

很不幸,这些权威的 CA 机构都是国外的,去年俄乌战争一开打,俄罗斯很多网站用的证书就被美国的 CA 机构给吊销了,结果导致网站无法访问,基本上就瘫痪了,老百姓取不了钱、上不了网,这对我们无疑也是一个警醒。

四、数字证书

大家可以多用毕业证书、学位证书、PMP 证书等等这些来理解数字证书和数字签名的概念。

下面我们看一下证书颁发的流程。

我们可以在服务器上用 openssl 命令生成一对公钥和私钥,然后将域名、申请者身份信息(国家/省份/城市/公司名称/部门名称)、公钥(注意不是私钥,私钥是无论如何也不能泄露的)等其他信息整合在一起,生成 .csr 文件(即证书签名请求文件)发给 CA 机构。这些操作也可以通过网站在线完成,省去了使用命令行的麻烦。

CA 机构收到申请后,会通过各种手段验证申请者的组织信息和个人信息,如无异常(组织存在/企业合法/确实是域名的拥有者,这些才算正常),CA 就会使用不可逆的散列算法对 .csr 里的明文信息先做一个 HASH,得到一个信息摘要,再用 CA 自己的私钥对这个信息摘要进行加密,生成一串密文,密文即是所说的签名(或者叫 CA 签名/数字签名)。签名 + .csr 明文信息,即是证书(或者叫数字证书),最后 CA 把这个证书返回给申请人,这样一份数字证书就颁发出来了。

在 CA 使用自己的私钥给我们生成签名的这个过程里,CA 叫 Issuer,我们叫 Subject。据此我们也可以想象,就是根证书的 Issuer 和 Subject,其实都是它自己。

证书颁发流程.jpg

注意 CA 是用自己的私钥对 csr 明文信息的摘要进行加密生成签名的,而不是对 csr 明文信息的全文进行加密生成签名,原因就是正文内容长而摘要内容短,对摘要进行签名更快也更合理。

证书颁发好之后,我们把它和最前面自己生成的私钥文件,这两个文件都放在服务器上(比如 nginx 存放 SSL 文件的目录下)。客户端通过 SSL/TLS 协议的握手过程收到这个证书后,用 CA 的公钥解密证书里面的签名信息(这个动作也叫验签),当然是可以解密成功的,因为这是用 CA 的私钥加密的,解密出来的就是摘要信息,客户端再对证书里的明文信息进行哈希,得到另一个摘要信息,它会比较自己解密出来的摘要和计算出来的摘要,如果一样,则表示证书里的明文信息是完整未篡改的(其实主要是为了确认明文信息里的服务端公钥信息是正确的),这样客户端后续就用这个公钥信息对通讯数据进行加密了。

也就是说后续客户端会使用服务端的公钥将数据加密后,发给服务端,服务端用对应的私钥信息将数据解密出来。同理,服务端返回数据给客户端之前,也先用自己的私钥加密,客户端收到后再用服务端的公钥解密出来,整个通讯过程是对称加密的,以便提高性能,只是在最前面的协商公钥环节用到了非对称加密(用 CA 公钥解密 CA 私钥加密后的签名)。

大家可能会问,为什么客户端可以用 CA 的公钥解密证书里面的签名信息?客户端哪来的 CA 公钥?是这样的,电脑/服务器/手机/电视等设备的操作系统出厂就自带了一批 CA 证书,也叫系统根证书,浏览器会使用到这些系统根证书,里面包含了所有国际权威 CA 机构的根证书。这些证书里面,就有他们的公钥。

那为什么要叫“根证书“呢?”根“这个字,表示最底层。大家想一想如果全球所有网站购买证书都要来这些为数不多的国际大 CA 机构操作,那他们不得忙死了?这么搞也非常不安全。

所以这些根证书 CA 机构搞了个中间证书 CA 出来,根 CA 用自己的私钥给中间 CA 的证书进行一个数字签名,表示中间 CA 的证书是有公信力的,中间证书 CA 再用他们的私钥给我们的服务器证书做数字签名,来表示我们的证书也是有公信力的,这样就形成了一个所谓的“证书链”机制,其实也是一种“信任链”机制。

针对这种使用了中间证书的情况,服务端可能会把中间证书,连同自己的域名证书,一并发给客户端。客户端会先提取出中间证书里的公钥信息,再提取出域名证书里的签名信息,然后用这个公钥去解密这个签名,得到域名证书的摘要信息(摘要信息里能看到使用的摘要算法)。之后,对域名证书里的明文信息部分(body 部分,不包含签名)进行信息摘要(用解密出来的哈希值里显示的那个摘要算法),算出的哈希值如果等于签名解密出来的哈希值,就说明这个域名证书确实是由该中间 CA 进行签名的。

同理,查看中间 CA 证书的明文信息,找到它里面的 Issuer,发现是一个根 CA,那就到系统根证书区域去找到这个根 CA 的公钥,用这个公钥去解密中间 CA 证书里的签名,得到一个哈希值,再对中间 CA 证书的明文信息部分(body 部分,不包含签名)按解密出来的哈希值里显示的摘要算法进行哈希,发现算出来的哈希和解密出来的哈希完全一样,就说明该中间证书确实是由根 CA 进行签名的。

上面的这个过程说起来复杂,但其实客户端处理起来是很快的。

当然,如果服务端没有给客户端返回中间证书,浏览器也会根据域名证书里的相关信息( AIA,Authority Information Access)去下载中间证书的信息进行验签及递归验签。其实,服务端是否返回中间证书,要看我们从 CA 那边购买并下载下来的证书文件里,是否选择了同时下载中间证书。如果选择了,那我们下载得到的 crt 或者 pem 证书文件里,会有不止一段的 -----BEGIN CERTIFICATE----- 与 -----END CERTIFICATE-----。服务器证书在上面,中间证书在下面。没有下载也没事,后续我们可以再去选择下载证书链文件。

当然,不管是根 CA 证书(根证书),还是中间 CA 证书(中间证书,中间证书可能有多层),还是我们自己的服务器域名证书(叶子证书),都可以叫数字证书。

五、自签名证书

有些场景下,可能会用到自签名证书。通过上面的描述可以知道,步骤有如下这些:

  1. 在服务器上生成 CA 根证书的私钥

    bash
    # 生成一个长度为 2048 位的私钥,存储在 ca.key 文件中
    openssl genrsa -out ca.key 2048
  2. 使用私钥生成 CA 根证书

    bash
    # 生成一个有效期为 3650 天的自签名 CA 根证书,存储在 ca.crt 文件中
    # 在生成证书时,需要输入一些关于组织和域名的信息,这些信息将被包含在证书中,这里由于是生成根证书,所以咱们可以不需要把待输入的域名设置成服务的域名,可以设置成一个通用的名称,比如叫 Zhuojian Root CA
    openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
  3. 在服务器上生成 server 证书的私钥

    bash
    # 生成一个长度为 2048 位的私钥,存储在 server.key 文件中
    openssl genrsa -out server.key 2048
  4. 生成 server 域名证书签名请求(certificate signing request,CSR)

    bash
    # 使用上一步生成的 server.key 文件,并将请求保存在 server.csr 文件中
    # 在生成域名 CSR 时,需要输入一些关于组织和域名的信息,这些信息将被包含在证书中,这一步需要注意,一定要设置成最终我们要使用的域名,最好是设置成指定的单域名,也可以设置成多域名或通配符域名
    openssl req -new -key server.key -out server.csr
  5. 生成 server 证书

    bash
    # 生成一个有效期为 365 天的 server 证书,存储在 server.crt 文件中
    # 在这个命令中,使用到了生成的私钥 ca.key 文件和 CA 根证书 ca.crt 文件
    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
  6. (可选,类似于上面的步骤 4 和步骤 5 )在 CSR 中包含 SAN(主题备用名称,浏览器通常读取这个,与请求的域名进行比较) 信息

    当我们要为一个证书支持多个域名或子域名时(例如,通配符证书 *.example.com 或同时支持 example.comsub.example.com ),需要在生成 CSR 的过程中包含 SAN 信息。以下是具体步骤:

    1. 创建 CSR 配置文件: 创建一个新的配置文件(例如 csr_with_san.cnf )来包含 SAN 信息。该文件可能如下所示:

      [req]
      default_bits = 2048
      prompt = no
      default_md = sha256
      distinguished_name = dn
      req_extensions = req_ext # 指定请求扩展
      [dn]
      C = CN
      ST = ZJ
      L = HZ
      O = ZJKJ
      OU = PD
      emailAddress = [email protected]
      CN = example.com # 主域名
      [req_ext]
      subjectAltName = @alt_names
      [alt_names]
      DNS.1 = example.com
      DNS.2 = *.example.com
      DNS.3 = sub.example.com

      [alt_names] 部分,我们可以列出所有需要支持的域名。如果在这里只写了一个域名,那就跟上面的 CN 部分写的域名保持一致,这样就会生成一个单域名证书。

    2. 使用配置文件生成 CSR: 使用带 SAN 信息的配置文件生成新的 CSR: openssl req -new -key server.key -out server.csr -config csr_with_san.cnf 这条命令使用之前创建的私钥 server.key 和新的配置文件 csr_with_san.cnf 来生成一个包含 SAN 信息的 CSR。

    3. 使用 CA 根证书签发带 SAN 的服务器证书: 按照之前的步骤,使用根证书和其私钥对该 CSR 进行签名: openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -extfile csr_with_san.cnf -extensions req_ext 注意这里使用了 extfileextensions 选项来确保 SAN 被包含在签发的证书中。

    通过以上步骤,就能生成一个支持多个域名的服务器证书,适用于更复杂的环境或配置要求。这样生成的证书在涵盖多个特定域名时会更加灵活和实用。

  7. 将服务器域名的证书和私钥配置到 Nginx 服务器上

    最后,我们将生成的 server.crt 和 server.key 文件拷贝到服务器上,并将它们配置到 nginx 的证书和私钥文件路径里。

很显然,我们还要在访问这个域名的电脑、应用服务器以及 nginx 服务器上,将我们的自签名 CA 根证书导入到系统根证书里(注意是导入 CA 根证书,而不是域名 server 证书)。只有这样,由我们这个自签名 CA 根证书颁发的 server 证书,才能被客户端及服务器信任。可参照这个 https://www.hash070.top/archives/linux-install-ca.html 网站进行 Linux 服务器的根证书导入,以及参照这个 https://blog.csdn.net/caoshiying/article/details/78668076 进行 Windows 服务器或 Windows PC 的系统根证书导入。

CA 根证书导入到系统后,应用服务器要配置自己的 hosts 文件,将要访问的 HTTPS 域名解析到内网的 nginx 服务器 IP 上。这样一样,在应用服务器上访问该 HTTPS 域名,请求会到达 nginx 服务器然后被 nginx 服务接收到,nginx 接收请求后再使用自己配置文件里配置的 server 证书及私钥文件,对流量进行解密,完成整个 SSL/TLS 握手过程,之后就可以正常进行代理转发了。

当然,我们也可以精简下,就是不要生成 CA 根证书了,直接生成自签名的 server 证书,然后在电脑和服务器上导入一下这个自签名的 server 证书到系统根证书里即可。

但是如果咱们要生成很多个不同域名的 server 证书,那先生成自签名的 CA 根证书还是很有必要的,因为我们可以用这个 CA 根证书,签发任意多个不同域名的 server 证书,到时候电脑和服务器上只要导入这一个 CA 根证书,后面所有由它签名的 N 个不同域名 server 证书就都能被系统所信任了。