【实验目的】
- 简单模拟地实现 TLS 的流程和最新的 TLS 1.3
【实验要求】
项目应包括两个部分:客户端和服务器
-
客户端和服务器可以通过socket建立连接实现相互通信
-
客户端和服务器应就加密的会话密钥(session key)达成一致,会话密钥应显示在服务器的屏幕或用户界面上。注意:会话密钥主要由客户端决定。
一个简单的方法如下: 首先,客户端生成会话密钥,然后使用服务器的公钥,客户端加密会话密钥并将其发送到服务器。希望可以想出更好的方法来生成会话密钥。
-
客户端加密一个字符串,计算 MAC 并发送给服务器。
-
服务器解密,验证 MAC。
【实验建议】
1、字符串加密方法应该是对称的,如DES、3DES,对称加密比不对称快得多。
2、可以使用 SHA-256 计算 MAC。
【实验步骤】
这幅图可以比较清晰说明采用RSA作为密钥协商算法的TLS握手协议流程:
在代码实现中,当socket连接建立后,分为以下几个部分(和实际上的TLS有一些差别):
Client Server
client_hello -------->
server_hello
server_certificate
server_key_exchange
certificate_request
<-------- server_hello_done
client_certificate
client_key_exchange -------->
<-------- receive_premastersecret
client_finish -------->
<-------- server_finish
generate_sessionkey <-------> generate_sessionkey
send_message <-------> receive_message
一、代码实现
1、Client Hello
当客户端第一次连接到服务器时,它需要发送ClientHello作为它的第一条消息。
访问一个https的网站,通过wireshark抓包,可以看到Client Hello中包含:客户端使用的 TLS 版本号(Version)、客户端生成的随机数(Random)、会话ID(Session ID)、支持的密码套件列表(Cipher Suite),支持的压缩算法(Compression Methods)和扩展(Extension)。
Version字段表示Client愿意在本次会话中使用的 TLS 协议的版本(会在后续验证PreMasterSecret中使用到)。
Random字段是一个32位随机数,之后用来生成会话密钥,在TLS实际实现中前4位是Unix时间戳,代码中简化了这一点(TLS并没有强制要求校验该时间戳,允许定义为其他值),直接生成了32位随机数。
Cipher Suite字段是Client所支持的密码套件列表。由于在TLS协议传输过程中,客户端和服务器必须使用同一套加解密算法才能保证数据可以正常加解密,所以一开始客户端就需要通过把本地支持的Cipher Suite列表传送给服务器,告诉服务器自己支持的加密算法。
而其他三个字段:Session ID主要用在会话恢复中,Compression Methods是Client所支持的压缩算法的列表,Extension使得Clients可以通过在扩展域中发送数据来请求Server的扩展功能。这三个字段在实验中简化省略了。
#TLSClient.py
def client_hello():
print("=============TLS 握手=============")
print("[]Client hello")
# 发送TLS版本
global client_version
client_version = b"01"
print("[]Client's version:" + str(client_version))
clientSocket.send(client_version)
time.sleep(0.5)
# 发送加密套件(Cipher Suite)
cipher_suite = "TLS_RSA_WITH_DES_SHA256"
# Cipher Suite的格式为TLS_密钥交换算法_身份认证算法_WITH_对称加密算法_消息摘要算法
# 由于RSA又可以用于加密也可以用于身份认证,因此密钥交换算法使用RSA时,只写一个RSA
print("[]Client's Cipher Suite:" + cipher_suite)
clientSocket.send(cipher_suite.encode())
time.sleep(0.5)
# 生成随机数
global clientRandom
clientRandom = getRandomInteger(32)
print("[]Client's random number:" + str(clientRandom))
# print((clientRandom).to_bytes(4, 'little'))
clientSocket.send(clientRandom.to_bytes(4, 'little'))
运行结果:
2、Server Hello
通过抓包可以看到ServerHello中包含:Version, Random, Session ID, Cipher Suite, Compression Method, Extensions。
Version字段将包含Client在Client Hello消息中建议的较低版本和Server所能支持的最高版本。
Random字段和Client Hello一样,是一个由Server生成的32位随机数,且独立于clientRandom
,之后同样用来生成会话密钥。
Cipher Suite字段是Server从Client提供的列表中选择的密码套件。
代码实现中,Server默认可以支持Client能支持的所有版本,Cipher Suite服务端只支持TLS_RSA_WITH_DES_SHA256
,Session 、Compression Methods、Extension字段省略。
若客户端支持的Cipher Suite不是TLS_RSA_WITH_DES_SHA256
,则发送"Cipher Suite Failed"
,Client收到后就会关闭套接字。
#TLSServer.py
def server_hello():
print("[]Server hello")
# 接收客户端版本
global client_version
client_version = connectionSocket.recv(1024)
print("[*]Receive Client's version:" + str(client_version))
# 接收客户端加密套件
cipher_suite = connectionSocket.recv(1024).decode()
print("[*]Receive Client's Cipher Suite:" + cipher_suite)
if cipher_suite == "TLS_RSA_WITH_DES_SHA256":
print("[*]Cipher Suite is OK")
connectionSocket.send("Cipher Suite is OK".encode())
# 接收客户端的随机数
print("[]Receive random number from client")
global clientRandom
clientRandom = connectionSocket.recv(1024)
clientRandom = int.from_bytes(clientRandom, 'little')
print("[*]The random number from client is:" + str(clientRandom))
# 产生一个32位的随机数发送给客户端
print("[]Send server's random")
global serverRandom
serverRandom = getRandomInteger(32)
print("[]Server's random is:" + str(serverRandom))
connectionSocket.send(serverRandom.to_bytes(4, 'little'))
else:
print("[]Cipher Suite Failed") # 客户端支持的Cipher Suite不是TLS_RSA_WITH_DES_SHA256
connectionSocket.send("Cipher Suite Failed".encode()) # 客户端收到消息后断开连接
运行截图:
3、Server Certificate
在Server Hello完成后,服务端需要将本地的证书传给客户端,该证书有2个作用:
- 客户端可以对服务端的证书进行合法性进行校验。
- 证书中包含服务端的公钥,之后Client可以用该公钥对PreMasterSecret加密,只有Server才能解密该信息。
#TLSServer.py
def server_certificate():
# 服务端需要将自己的证书发送给客户端,用来告诉客户端自己可信。
# 发证书
print("[]Send server cert")
cert_file_path = 'server.crt'
serverCert = open(cert_file_path).read()
connectionSocket.send(serverCert.encode())
【以下是创建证书的具体过程】
①建立CA
在文件夹key中生成CA私钥。
genrsa
:生成rsa秘钥;-out ca.key
生成的秘钥文件为ca.key
;2048: 秘钥长度为2048。
➜ key openssl genrsa -out ca.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
..+++++
....+++++
e is 65537 (0x010001)
用CA私钥生成CA的证书,-x509
选项会生成自签名证书。
➜ key openssl req -new -x509 -days 365 -key ca.key -out ca.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:fdu
Organizational Unit Name (eg, section) []:fdu
Common Name (e.g. server FQDN or YOUR name) []:127.0.0.1
Email Address []:test@qq.com
在key文件夹下建立CA相应目录
➜ key mkdir demoCA
➜ key cd demoCA
➜ demoCA mkdir newcerts
➜ demoCA touch index.txt
➜ demoCA echo '01' > serial
②生成server端证书
进入key文件夹,生成server私钥
➜ key openssl genrsa -out server.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
..........+++++
..............+++++
e is 65537 (0x010001)
生成server公钥:
➜ key openssl rsa -in server.key -pubout -out server_public.key
writing RSA key
使用server私钥生成server端证书请求文件(没有-x509选项则生成证书请求文件。)
➜ key openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:fdu
Organizational Unit Name (eg, section) []:fdu
Common Name (e.g. server FQDN or YOUR name) []:127.0.0.1
Email Address []:test@qq.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:12345678
An optional company name []:tls
使用server证书请求文件通过CA生成自签名证书
➜ key openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key
Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: Nov 11 11:20:26 2021 GMT
Not After : Nov 11 11:20:26 2022 GMT
Subject:
countryName = CN
stateOrProvinceName = Shanghai
organizationName = fdu
organizationalUnitName = fdu
commonName = 127.0.0.1
emailAddress = test@qq.com
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
F6:17:A4:F1:64:6B:C2:22:D0:AC:DA:E0:6C:D1:D5:FB:E4:81:E3:82
X509v3 Authority Key Identifier:
keyid:DF:21:7B:45:42:1B:A3:81:0E:7E:E3:91:E6:C6:CD:5D:1D:03:98:D3
Certificate is to be certified until Nov 11 11:20:26 2022 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
验证server证书
➜ key openssl verify -CAfile ca.crt server.crt
server.crt: OK
4、Server Key Exchange
如果服务端向客户端发送的证书中没有提供足够的信息(证书公钥)的时候,可以向客户端发送一个 Server Key Exchange。
(虽然我生成的RSA证书中是有公钥的,但是我提取失败了所以就把公钥再发一遍。)
#TLSServer.py
def server_key_exchange():
# 发公钥
# 查到的资料是可以通过crypto.load_certificate和crypto.dump_publickey从证书提取公钥,但是失败了
print("[]Send server public key")
key_file_path = 'server_public.key'
key = open(key_file_path).read()
connectionSocket.send(key.encode())
time.sleep(0.5) # 因为receive的顺序可能有问题,这里停0.5s
2022.07.01 UPDATE:
后来想到的办法是可以
import sys
,通过命令行执行openssl命令,提取公钥和验证证书
5、Certificate Request
当需要TLS双向认证的时候,服务端需要验证客户端的证书,向客户端发送Certificate Request请求获取客户端指定类型的证书。
#TLSServer.py
def certificate_request():
# 向客户端发出 Cerficate Request 消息,要求客户端发送证书对客户端的合法性进行验证
print("[]Cerficate Request")
cer_req = "Cerficate Request:PLZ send me your cert"
connectionSocket.send(cer_req.encode())
6、Server Hello Done
当服务端处理Hello请求结束时,发送Server Hello Done消息,Server 将会等待 Client 发过来的响应。
#TLSServer.py
def server_hello_done():
SH_done = "Server Hello Done"
print("[]" + SH_done)
connectionSocket.send(SH_done.encode())
以上4步运行截图:
7、Client Certificate
这里客户端收到客户端发来的server_hello、server_certificate、server_key_exchange、certificate_request,其中:
- 收到客户端关于Cipher Suite的回复,如果收到Failed,则说明Server不支持该加密套件,关闭连接。
- 收到Sever产生的随机数并保存。
- 收到客户端的证书,对证书进行验证。
- 收到客户端公钥。
- 收到了Cerficate Request,向服务器发送客户端的ca证书。
- 收到Server Hello Done。
#TLSClient.py
def client_certificate():
# ①收到Cipher Suite is OK/failed
print("[]Receive Cipher Suite State")
recv_cs = clientSocket.recv(1024).decode()
print("[*]" + recv_cs)
if recv_cs[13:] == "Failed":
clientSocket.close()
# ②收到随机数
print("[]Receive random number from server")
global serverRandom
serverRandom = clientSocket.recv(1024)
serverRandom = int.from_bytes(serverRandom, 'little')
print("[*]The random number from server is:" + str(serverRandom))
# ③收到证书,客户端需要对服务端的证书进行检查
# 这一步验证也是失败了,所以只是单纯地接收证书
print("[]Receive Server's Certificate")
serverCert = clientSocket.recv(5000).decode()
# ④收到公钥,实际上客户端可以从证书中提取公钥,但是没能实现
print("[]Receive public key")
global public_key
public_key = clientSocket.recv(1024)
# ⑤收到了Cerficate Request,向服务器发送客户端的ca证书
print("[]Receive Cerficate Request")
cer_req = clientSocket.recv(1024).decode()
print("[*]" + cer_req)
print("[]Send ca cert")
cert_file_path = 'ca.crt'
clientCert = open(cert_file_path).read()
clientSocket.send(clientCert.encode())
# ⑥收到"Server Hello Done"
print("[]Receive Server Hello Done")
SH_Done = clientSocket.recv(1024).decode()
print("[*]" + SH_Done)
8、Client Key Exchange
在TLS中,如果密钥协商和身份认证的算法是RSA,在Client接收到Server Hello Done消息后,会生成一个 48 字节的预备主密钥(PreMasterSecret),使用 Server 的公钥进行RSA加密,然后以一个加密的预备主密钥消息发送给Server,以保证只有Server的私钥才能解密。
48 字节的PreMasterSecret由两部分组成:2字节的client_version(Client Hello中包含的内容)+46字节的随机数。2字节的client_version用于Server解密后的核对,确保PreMasterSecret没有被篡改。
发送Encrypted PreMasterSecret后,客户端切换到加密套件,这里简化步骤,直接向Server发送”客户端已切换到加密套件”。
#TLSClient.py
def client_key_exchange():
# 这里通过RSA交换算法达成密钥协商
# Client随机生成46字节+2字节的client_version,作为pre master secret
# pre master secret使用Server的公钥加密,以一个加密的预备主密钥消息发送给Server
rand = getRandomInteger(368)
rand = rand.to_bytes(46, "little")
global client_version
print("[]PreMasterSecret(bytes):" +str(client_version + rand))
global PreMasterSecret
PreMasterSecret = int.from_bytes(client_version + rand, "little")
print("[]PreMasterSecret(int):" + str(PreMasterSecret))
# RSA加密
print("[]开始RSA加密")
global public_key
pub_key = public_key.decode()
pub_key = RSA.importKey(str(pub_key))
cipher = PKCS1_cipher.new(pub_key)
PreMasterSecret = base64.b64encode(cipher.encrypt(PreMasterSecret.to_bytes(48, 'little')))
print("[]Encrypted PreMasterSecret:" + PreMasterSecret.decode('utf-8'))
clientSocket.send(PreMasterSecret)
clientSocket.send("客户端已切换到加密套件".encode())
运行截图:
9、Receive Premastersecret
在这一部分,Server主要做三件事:
- 接收客户端的ca证书并验证(同样省略验证步骤)
- 接收Client发送的Encrypted PreMasterSecret,用RSA私钥进行解密。解密以后校验 PreMasterSecret 中的 ProtocolVersion 和 ClientHello 中传递的 ProtocolVersion 是否一致,若不一致则发送失败信息,客户端断开连接。
- 若验证成功则Server也切换到加密套件。
#TLSServer.py
def receive_premastersecret():
# 接收客户端的ca证书
print("[]Client Certificate Verify")
serverCert = connectionSocket.recv(5000).decode()
# Server 拿到 Encrypted PreMasterSecret 以后,用自己的 RSA 私钥解密。
# 解密以后校验 PreMasterSecret 中的 ProtocolVersion 和 ClientHello 中传递的 ProtocolVersion 是否一致。
global PreMasterSecret
PreMasterSecret = connectionSocket.recv(1024)
print("[]Receive PreMasterSecret:" + PreMasterSecret.decode())
# 接收消息:客户端已切换到加密套件
recv = connectionSocket.recv(1024).decode()
print("[*]" + recv)
with open('server_private.key') as f:
key = f.read()
pri_key = RSA.importKey(key)
cipher = PKCS1_cipher.new(pri_key)
back_text = cipher.decrypt(base64.b64decode(PreMasterSecret), 0)
recv_version = back_text[:2]
global client_version
if recv_version == client_version:
print("[√]客户端验证成功")
back_text = int.from_bytes(back_text, 'little')
print("[]PreMasterSecret解密结果:" + str(back_text))
connectionSocket.send("客户端验证成功,服务器已切换到加密套件".encode())
else:
connectionSocket.send("客户端验证失败".encode())
运行截图:
10、Client Finish
这一部分,Client主要做两件事:
- 收到Server反馈的验证消息,如果验证失败,说明发送的Encrypted PreMasterSecret被篡改,连接不安全,Client选择终止socket连接。
- 如果连接成功,Client通过之前Client Hello中的ClientRandom、Server Hello中的ServerRandom和Client Key Exchange中的PreMasterSecret一起生成MasterSecret。
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
在RFC5246(The Transport Layer Security (TLS) Protocol Version 1.2)的section-8.1中介绍了MasterSecret的生成方法。是由ClientRandom、ServerRandom、PreMasterSecret通过PRF()
函数生成,PRF()
函数是基于 HMAC 定义的,所以代码中采用了HMAC_SHA256,其中密钥是PreMasterSecret。
#TLSClient.py
def client_finish():
print("[]Client Finish")
# 收到验证消息
print("[]Receive PreMasterSecret verify")
recv = clientSocket.recv(1024).decode()
if recv[5:7] == "失败":
print("[!]验证失败")
clientSocket.close()
else:
print("[√]验证成功")
print("[*]" + recv)
# 根据ClientRandom、ServerRandom、PreMasterSecret计算MasterSecret
print("[]Generate MasterSecret")
global clientRandom, serverRandom, PreMasterSecret
clientrandom = clientRandom.to_bytes(4, "little")
serverrandom = serverRandom.to_bytes(4, "little")
premastersecret = PreMasterSecret
# 用HMAC_SHA256生成MasterSecret
global MasterSecret
MasterSecret = premastersecret + clientrandom + serverrandom
MasterSecret = base64.b64encode(hmac.new(premastersecret, MasterSecret, digestmod=sha256).digest())
print("[]对称加密公钥MasterSecret:" + str(MasterSecret))
运行结果:
11、Server Finish
同样,在Server端也需要将ClientRandom、ServerRandom、PreMasterSecret通过HMAC_SHA256生成MasterSecret。这样做的好处是对称加密密钥不会在网络中发送,都是双方在本地计算得出的。
#TLSServer.py
def server_finish():
print("[]Server Finish")
# 根据ClientRandom、ClientRandom、PreMasterSecret计算MasterSecret
print("[]Generate MasterSecret")
global clientRandom, serverRandom, PreMasterSecret
clientrandom = clientRandom.to_bytes(4, "little")
serverrandom = serverRandom.to_bytes(4, "little")
premastersecret = PreMasterSecret
global MasterSecret
MasterSecret = premastersecret + clientrandom + serverrandom
MasterSecret = base64.b64encode(hmac.new(premastersecret, MasterSecret, digestmod=sha256).digest())
print("[]对称加密公钥MasterSecret:" + str(MasterSecret))
运行结果:
从运行结果中可以看出,Client和Server计算出的MasterSecret是相同的。至此TLS握手协议就结束了,下面进入发送和接收消息的环节。
12、Generate Sessionkey
在RFC5246#section-10中介绍了Session Key即key_block的生成方法:
When keys and MAC keys are generated, the master secret is used as an
entropy source.
To generate the key material, compute
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
until enough output has been generated. Then, the key_block is
partitioned as follows:
client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length] //最后两个有特殊用途,这里用不上
server_write_IV[SecurityParameters.fixed_iv_length]
所以在代码中Session Key被分为六个部分:
- 客户端写入MAC密钥
client_write_MAC_key
:客户端用来创建MAC,服务器用来验证MAC。 - 服务器写入MAC密钥
server_write_MAC_key
:服务器用来创建MAC,客户端用来验证MAC。 - 客户端写入加密密钥
client_write_key
:客户端用来加密数据,服务器用来解密数据。 - 服务器写入加密密钥
server_write_key
:服务器用来加密数据,客户端用来解密数据。 client_write_IV
和server_write_IV
这两个有其他用途,这里用不上
#TLSClient.py&TLSServer.py
def generate_sessionkey():
print("\n=============Generate SessionKey=============")
# 用HMAC_SHA256生成SessionKey
global clientRandom, serverRandom, MasterSecret
clientrandom = clientRandom.to_bytes(4, "little")
serverrandom = serverRandom.to_bytes(4, "little")
mastersecret = MasterSecret
SessionKey = mastersecret + serverrandom + clientrandom
SessionKey = base64.b64encode(hmac.new(mastersecret, SessionKey, digestmod=sha256).digest())
print("[]SessionKey:"+str(SessionKey))
# 划分为4个部分,需要注意后续加密用的是DES,密钥必须是8个字节
# 所以前16个字节用作数据加密相关密钥
global client_write_key, server_write_key, client_write_MAC_key,server_write_MAC_key
client_write_key = SessionKey[:8]
print("[]client_write_key:"+str(client_write_key))
server_write_key = SessionKey[8:16]
print("[]server_write_key:" + str(server_write_key))
client_write_MAC_key = SessionKey[16:30]
print("[]client_write_MAC_key" + str(client_write_MAC_key))
server_write_MAC_key = SessionKey[30:]
print("[]server_write_MAC_key" + str(server_write_MAC_key))
运行截图:
13、Send Message&Receive Message
对于应用程序数据,TLS通过Mac-then-Encrypt的方法,实现了数据加密和MAC计算,即在明文上计算MAC,将其附加到数据,然后加密明文+MAC的完整数据。
Client通过生成的client_write_MAC_key
对明文进行HMAC_SHA256,再把MAC+明文通过DES加密(密钥是client_write_key),把加密后的数据传输给Server。
Server通过client_write_key
解密,再把MAC和明文切分,对明文用client_write_MAC_key
计算MAC,和收到的MAC进行比较,如果不一致,说明消息被篡改。
#TLSClient.py
def send_message():
generate_sessionkey()
global client_write_key, server_write_key, client_write_MAC_key, server_write_MAC_key
print("\n=============Send Message=============")
# 先用HMAC_SHA256计算MAC(46bytes)
# 提交的代码中把message改为用户可自己在终端上进行输入
message = "I am doing the TLS project."
print("[]Plain Message:" + message)
mess_mac = base64.b64encode(hmac.new(client_write_MAC_key, message.encode(), digestmod=sha256).digest())
print("[]Message MAC:" + str(mess_mac))
message = str(mess_mac) + message
print("[]Message:" + message)
# 再用DES加密
des_key = client_write_key
print("[]DES密钥:" + str(des_key))
des = DES.new(des_key, DES.MODE_ECB) # 创建DES实例
padded_text = pad(message)
encrypted_text = des.encrypt(padded_text.encode('utf-8')) # 加密
print("[]DES加密结果:" + str(encrypted_text))
clientSocket.send(encrypted_text)
print("===========Send Message Over===========\n")
#TLSServer.py
def receive_message():
generate_sessionkey()
global client_write_key, server_write_key, client_write_MAC_key, server_write_MAC_key
print("\n=============Receive Message=============")
encrypted_text = connectionSocket.recv(1024)
# DES解密
des_key = client_write_key
print("[]DES密钥:" + str(des_key))
des = DES.new(des_key, DES.MODE_ECB)
plain_text = des.decrypt(encrypted_text).decode().rstrip(' ') # 解密
print("[]DES解密结果:" + plain_text)
recv_mac = plain_text[:47]
print("[]Received MAC:" + recv_mac)
message = plain_text[47:]
print("[]Plain Message:"+message)
# server根据message计算MAC,和recv_mac比较,如果不一样,说明消息被篡改过
mess_mac = base64.b64encode(hmac.new(client_write_MAC_key, message.encode(), digestmod=sha256).digest())
# mess_mac = hashlib.sha256(message.encode('utf-8')).hexdigest()
print("[]Message MAC:" + str(mess_mac))
if str(mess_mac) != recv_mac:
print("[!]消息被篡改")
else:
print("[√]消息认证通过")
print("[√]Message:" + message)
运行截图:
Client:
Server:
Client&Server完整运行结果:
TLSServer:
TLSClient:
二、How to produce the session key
关于如何产生Session Key,总体流程上参考了TLS1.2
RFC文档中的介绍,采用的是基于RSA的密码协商和身份认证。
TLS 1.2 密钥计算流程如图所示:
具体的实施如下:
1、在Client与第一次连接到Server时,发送的Client Hello信息中包含Client生成的32位随机数clientRandom
;
2、在Server返回的Server Hello中包含Server生成的32位随机数serverRandom
;
3、在Client Key Exchange中,Client生成48字节的预备主密钥PreMasterSecret
,其中两字节是Client支持的协议版本,46字节是随机数,Client使用Server的公钥对PreMasterSecret
加密后发送给Server,确保只有Server可以用自己的私钥解密;
4、至此双方都掌握了三个随机数:clientRandom
、serverRandom
、PreMasterSecret
,基于这三个数通过HMAC_SHA256
生成主密钥MasterSecret
,其中PreMasterSecret
作为HMAC的密钥,由于MasterSecret
都是双方在本地生成的,不会在网络上传输,确保了机密性;
5、然后在每一次收发信息时,双方再通过MasterSecret
、serverRandom
和clientRandom
通过HMAC_SHA256
生成Session Key,把Session Key切分为4个部分:client_write_MAC_key
, server_write_MAC_key
, client_write_key
, 和server_write_key
。
优点:
(1)其中clientRandom
、serverRandom
、PreMasterSecret
三个随机数都非常有必要,确保了生成的密钥的随机性。
因为SSL协议中证书是静态的,所以有必要引入随机因素来确保协商出来的密钥的随机性。SSL协议并不信任每个主机都能产生完全随机的随机数,但是一个伪随机可能完全不随机,三个伪随机就十分接近随机了。所以通过三个随机值确保了MasterSecret
的随机性。
并且这样可以鉴别用户身份,防止重放攻击,只要任何一个通信方,接收到的报文中的随机数出现了重复,就说明有中间者干扰了通信的过程,可以立即中断通信。
(2)主密钥MasterSecret
不直接用于加密数据,而是通过HMAC后生成4个密钥,分别用于Client/Server对于数据的加密解密和对于MAC的计算。因为有HMAC,TLS支持检测数据的完整性。并且用不同的PRF()
可以生成任意长度的密钥,满足不同场景的需求。
缺点:
因为使用的是RSA,根据RSA算法的特性,如果Server的私钥泄露了,那么以前劫持的会话记录都可以被解析。
所以实际中TLS使用的密钥协商协议更多的还是Diffie Hellman算法。
三、Heartbleed
漏洞简介:
Heartbleed(CVE-2014-0160)是一个出现在加密程序库OpenSSL的安全漏洞,该程序库广泛用于实现互联网的传输层安全(TLS)协议。
这个漏洞之所以被命名为Heartbleed,是因为用于TLS/DTLS的Heartbeat扩展存在漏洞。Heartbeat扩展为TLS/DTLS提供了一种新的简便的连接保持方式。该拓展的功能是:正常情况下要使SSL发挥作用,需要与Server进行通信,所以会发送一个“heartbeat”包,通过包内的特定信号,进行判断Server是否存活,服务器就会向计算机返回该信号,让服务器确保安全通信。而且计算机和服务器之间会定期发送hearbeat以确定用户和服务器没有脱机。
但是由于产生了边界错误,没有在memcpy()
调用用户输入内容作为长度参数之前,正确地进行边界检查。攻击者可以追踪OpenSSL所分配的64KB缓存、将超出必要范围的字节信息复制到缓存当中再返回缓存中,这样一来受害者的内存内容就会以每次64K的速度进行泄露。攻击者利用漏洞披露连接的客户端或服务器的存储器内容,导致攻击者不仅可以读取其中机密的加密数据,还能盗走用于加密的密钥。
漏洞原理:
// d1_both.c,Heartbeat扩展源码
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
假如客户端发送的data数据为”007abcdefg”,那么服务器端解析可以得到type=0, payload=07, pl=’abcdefg’,申请(1+2+7=10)大小的内存。
由于用户可控制变量payload
和pl
,影响到了memcpy函数,导致如果用户并没有在心跳包中给足够多的数据,比如pl指向的数据只有一个字节,memcpy
会将这条SSLv3后面的数据以payload为长度复制出来。
修复方案:
1、OpenSSL升级到没有漏洞的版本 2、重新编译openssl,在编译时增加参数 -DOPENSSL_NO_HEARTBEATS 关闭心跳扩展功能
启发:
Heartbleed本质上还是缓存区溢出漏洞,因为对于用户的输入不加以检查导致可以构造恶意payload泄露数据。所以在写程序的时候一定要对用户的输入加以限制或检查。