数字证书 介绍

密码学基础知识

对称加密

在“非对称加密”的概念诞生之前,人类一直都是使用“对称加密”进行信息加密传输。
对称加密
如上图所示,对称加密原理十分简单:发信者通过密钥K对明文加密,收信者使用相同的密钥K对密文进行解密。

一个典型的对称加密算法就是“凯撒密码”:
凯撒密码
在凯撒密码中,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。在凯撒密码的算法中,“密钥K” 即字母的固定偏移量,加密算法 即 明文加密钥K,解密算法 即 密文减密钥K。

对称加密的问题

  1. 密钥K的同步过程不安全:在整个通信过程中,发信方A和收信方B所掌握的密钥必须是相同的,一旦密钥变更,双方就需要再次同步密钥,要如何安全的同步密钥就成了一个问题。
  2. 密钥管理的开销过大:任意一对用户通信时,都必须拥有一个只属于这对用户的密钥。当大量的用户相互进行两两通信时,密钥的数量会呈几何级数增长,密钥的管理会变得非常困难。

非对称加密

为了解决对称加密中的种种问题,1976年美国学者 Whitfield Diffie 和 Martin Hellman 提出了非对称加密的概念,并创造了最早的非对称加密算法“Deffie-Hellman算法”。

在非对称加密中,每个用户均有一个密钥对,其中包含两个密钥。其中一个密钥向外部公开,被称为公钥(Public Key);另一个密钥用户秘密保存,被称为私钥(Private Key)。
非对称加密信息传输过程
非对称加密的信息加密传输过程如上图所示,发信者A通过公开渠道获取收信者B的公钥,使用B公钥对明文进行加密;B收到密文后,再用自己的私钥进行解密。

非对称加密解决了对称加密中存在的问题:收信者的公钥本就是对外公开的,不需要保密,不存在公钥的传输安全问题;每个用户都有一个密钥对,需要与其他人通信时,只要通过公开渠道获取到目标的公钥即可,不存在密钥管理的开销问题。

但是,由于非对称加密的算法相对比较复杂,所以其效率比起对称加密而言通常会更低。

典型的非对称加密算法——RSA算法

RSA算法是使用最为广泛的非对称加密算法之一,该算法于1977年由Ron Rivest、Adi Shamir和Leonard Adleman共同提出。

RSA算法基于数论中的大素数分解问题——对于两个大素数,求得其乘积是比较简单的;但由两个大素数的乘积反推两个大素数的值,在计算上是不可行的。

RSA算法是分组加密算法,即需要将待加密信息分成若干个信息块后,每个信息块依次加密。RSA算法的实现思路如下:

  1. 生成两个大素数 p p p q q q
  2. 计算两个数的乘积 n = p × q n=p\times q n=p×q
  3. 计算比 n n n 小且与 n n n 互质的数的个数, φ ( n ) = ( p − 1 ) ( q − 1 ) \varphi(n)=(p-1)(q-1) φ(n)=(p1)(q1)
  4. 选择一个随机数 e e e,使得 e e e 满足: 1 < e < n 1<e<n 1<e<n,且 e e e φ ( n ) \varphi(n) φ(n) 互质;
  5. 解方程 e ∙ d = 1 ( m o d   φ ( n ) ) e\bullet d=1(mod\ \varphi(n)) ed=1(mod φ(n)),求出 d d d 的值;
  6. ( e , n ) (e,n) (e,n) 为公钥,对外发布; ( d , n ) (d,n) (d,n)为私钥,保密;

若明文为 m m m,密文为 c c c,加密算法为:

c i ≡ ( m i ) e m o d   n c_i\equiv(m_i)^e mod\ n ci(mi)emod n

解密算法为:

m i ≡ ( c i ) d m o d   n m_i\equiv(c_i)^d mod\ n mi(ci)dmod n

具体数学理论基础和推导可参考 RSA算法基础详解

python中可使用 rsa库模拟RSA算法:

import rsa
(pubKey, priKey) = rsa.newkeys(1024)
message = '西安电子科技大学'
crypto = rsa.encrypt(message.encode('utf8'), pubKey) # 使用公钥加密
decrypto = rsa.decrypt(crypto, priKey)  # 使用私钥解密
print()
print('明文:' + message)
print('公钥:' + str(pubKey.e))
print('私钥:' + str(priKey.d))
print('密文(二进制):' + str(crypto))
print('解密后明文:' + decrypto.decode('utf8'))  # 将解密后文件用utf8解码
print()

RSA算法模拟

数字签名

在上文中,介绍了非对称加密中的信息传输过程,其中用公钥加密信息,私钥解密信息。那么,我们能不能使用私钥加密信息,公钥解密信息呢?答案是可以的,这就是数字签名。

数字签名原理
数字签名的原理如上图所示,A使用自己的私钥加密明文,B再使用A的公钥解密密文。

对于一个签名而言,它必须要有以下几个特性:

  1. 防冒充:其他人不能冒充你的签名;
  2. 防篡改:已经签名过的文件不能再被随意修改;
  3. 防抵赖:自己签名的文件,不能抵赖;
  4. 可验证:其他人可以验证签名的正确性;

对于数字签名而言,它符合上述特性:由于A的私钥只有A知道,所以其他人不可能冒充A进行加密操作;已经加密后的密文,其他人篡改任意一个字符,都将导致无法解密;该密文只能由A的公钥进行解密,一旦一个文件能用A公钥解密,那就可以断定该文件是由A的私钥进行加密的;其他人只需要用A的公钥试一试能否解密文件,即可知道该文件是否是A加密的。

在数字签名的实际使用中,常常是搭配Hash函数构造算法的。

Hash函数

Hash函数,也称散列函数、哈希函数或杂凑函数。该函数的主要作用是将任意长度的输入二进制值串映射成较短的固定长度的输出二进制值串,输出的二进制值串称为输入的Hash值。

Hash函数的主要特征为:

  1. 输入可以是任意长度,输出必须是固定长度;
  2. 从Hash值不能反向推导出输入;
  3. 对输入数据比较敏感,任意对输入数据的改动都会使输出产生较大编号;
  4. 对于不同的输入数据,Hash值相同的概率很小;
  5. Hash函数算法效率要高;

一种基于RSA算法和Hash函数的数字签名方案

基于RSA算法和Hash函数的数字签名方案
上图是一种基于RSA算法和Hash函数的数字签名方案:

  1. A 将消息x经过Hash函数后,再使用自己的私钥加密Hash值,得到消息x的数字签名;
  2. A 将 消息x的数字签名 和 消息x 一同发送给 B;
  3. B 使用 A的公钥解密 消息x的数字签名,得到消息x的Hash值;
  4. B 将 消息x 通过与A相同的Hash函数,得到消息x的Hash值;
  5. 比较这 两个Hash值 是否相同,若相同则说明消息x是未被修改的;

在该方案中,消息x先经过Hash函数得到Hash值,再对Hash值进行加密。如果直接对消息x进行加密,因为消息x的长度可能是非常大的,所以加密时消耗的时间会很大。但是,先将消息x经过Hash函数,由于Hash函数的输出长度是固定的,加密算法的耗时也就是基本不变的,提升了整个算法的效率。


数字证书

在非对称加密的运用过程中,无论是信息的传输还是数字签名,都需要通过公开渠道获取公钥。这个过程很可能被攻击者利用,使得用户一开始就获取了错误的公钥。如下图所示:

获取错误公钥
用户C想要获取A的公钥,而攻击者B将自己的公钥伪造成A的公钥发布。这样,用户C就用伪造的公钥验证了伪造的签名,A和C之间的通信将完全暴露给B。

为了防止这种情况的发生,就必须存在一个各方均可以信任的渠道,发送者将其公钥通过该信任渠道发布,而接收者通过该信任渠道获取其他人的公钥。

在实际生活中,这个信任渠道就是 证书认证中心(Certification Authority,CA),公钥则通过公钥证书进行发布。

公钥证书的生成

公钥证书的生成步骤十分简单,证书申请者将自己的公钥和个人信息发送给CA,CA审核后产生数字证书。

如下图所示,A生成一个密钥对,其中私钥自己保存,公钥连同其个人信息作为证书申请,发送给CA。
A发送证书申请
如下图所示,CA获取到用户的公钥和个人信息后,将该数据块使用CA的私钥进行数字签名,数字签名与原数据块共同组成了新的数据结构,这就是公钥证书。
数字证书的生成
公钥证书的使用过程与数字签名的验证过程一致,如下图所示:

公钥证书的使用
使用者用CA的公钥对CA的数字签名进行解密,得到B的公钥的Hash值;再让B的公钥通过Hash函数(图中未画出),再对两个Hash值进行比较;若比较结果一致,则可认定该公钥就是B的公钥。

X.509证书标准

目前应用最广的公钥证书是遵循ITUT X.509国际标准的证书。其具体格式及定义可以参考 rfc2459:Internet X.509 公钥基础设施:证书和 CRL 简介。这里简单介绍以下其具体结构。

X.509证书结构

X.509 v3证书结构
X.509证书目前一共更新了三个版本,分别是V1、V2、V3版本,V2版本中添加了“颁发者唯一标识符”和“主体唯一标识符”,V3版本添加了“扩展”。

  1. 版本号:该证书的版本;
  2. 序列号:CA给该证书的标识符,同一CA所颁发的序列号是唯一的,即如果该CA已经颁发过序列号为1的证书,那么它不能再颁发序列号为1的证书;
  3. 算法标识:CA数字签名时采用的算法;比如Hash函数采用 SHA256,加密算法采用RSA,那么该值就为 “sha256RSA”;
  4. 发布者:该证书的颁发者,即CA的各项信息;
  5. 有效期:该证书的有效期;
  6. 主体:该证书的持有者的各项信息;
  7. 公钥信息:公钥的各项信息,包括该公钥使用的算法,算法中需要的参数,公钥本身;
  8. 扩展:v3版本新增的可选项,为了提供更多的附加信息,是人为可以扩充的域;

如下图,即为CSDN官网的公钥证书:

Windows下CSDN官网公钥证书展示界面

生成公钥证书的Python代码

可以使用 pyopenssl 来实现公钥证书的生成,它是OpenSSL的python实现,其官方文档为 OpenSSL — Python interface to OpenSSL

from OpenSSL.crypto import *


def generateCert(serialNum:int, year:int, issuerInfo:X509Name, caCert:X509, caKey:PKey):
  """证书生成

  Args:
      serialNum (int): 序列号
      year (int): 有效期年份
      issuerInfo (X509Name): 申请人信息
      caCert (X509): CA的证书
      caKey (PKey): CA的私钥

  Returns:
      X509,Pkey: 生成的证书、密钥
  """
  key = PKey() # 创建私钥对象
  key.generate_key(TYPE_RSA, 2048)  # 生成RSA密钥对
  certReq = X509Req()  # 创建证书请求
  subject = certReq.get_subject()
  subject = issuerInfo	#将申请人信息赋给certReq的subject
  certReq.set_pubkey(key) # 设置公钥
  certReq.sign(caKey, 'sha256') # 签名
  cert = X509()	#创建证书类
  cert.set_version(2) # 设置版本号
  cert.set_serial_number(serialNum) # 设置序列号
  cert.set_issuer(caCert.get_subject()) # 设置颁发者
  cert.set_subject(certReq.get_subject()) # 设置使用者
  cert.set_pubkey(certReq.get_pubkey()) # 设置公钥
  cert.gmtime_adj_notBefore(0) # 设置有效期开始时间为当前
  cert.gmtime_adj_notAfter(year*365*24*60*60) # 设置有效期截止时间(年)
  cert.sign(caKey, 'sha256') # 签名
  return cert, key	#返回证书、密钥

代码中,我们先创建了一个证书请求,然后将申请人的信息赋给了证书请求,最后再将证书请求中申请人的信息赋予了证书本身。

为什么我们不直接将申请人的信息赋予给证书本身呢,具体可参考 Stackoverflow——How to generate a certificate using pyOpenSSL to make it secure connection?。直接赋予生成出来的证书虽然也是可识别的,但是在实际使用时服务器或浏览器会认定该证书是不安全的。


结束语

本文大部分图片均由 drawio 绘制,部分图片来源于百度百科。本文可随意转载。
本文若有任何错误和纰漏,欢迎指正。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值