目 录CONTENT

文章目录

【Python】深入理解TLS协议(附Python实现)

EulerBlind
2025-07-01 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

前置知识

TLS/SSL构成

TLS/SSL的功能实现主要依赖于三类基本算法:**散列(哈希)函数 Hash对称加密非对称加密**,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列(哈希)函数验证信息的完整性。** **在这里插入图片描述 可以理解为,TLS/SSL干了两件事,其一是保证传输内容不被篡改,最常用保证内容不被篡改的方法就是使用散列算法,以MD5为例,通过对传输内容计算MD5值,通过对比MD5值来验证接收数据是否完整。只有MD5只能保障数据传输完整,但不能保障别人无法看到内容,所以需要对传输的数据进行加密,这就是第二件事。

加密算法

对称加密

使用一个密码对内容进行加密和解密。常见的有DES,AES,IDEA算法。

加密:密文=加密算法(明文,密码)

解密:明文=解密算法(密文,密码)

关键点在于****加密和解密使用的同一密码,且加密/解密区别于对应算法的两种不同模式。需要注意的是,在安全领域中,该算法中的密码保存本身就是一个不安全的问题。

非对称加密

非对称加密算法指的是加、解密使用****不同的密钥,一把为公开的公钥,另一把为私钥。公钥加密的内容只能由私钥进行解密,反之由私钥加密的内容只能由公钥进行解密。也就是说,这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容只能由对方进行解密。

加密:公钥加密,私钥解密的过程,称为「加密」

签名:私钥加密,公钥解密的过程,称为「签名」

非对称加密例子:让A写下一个任意3位数,并将这个数和91相乘;然后将积的最后三位数告诉B,这样B就可以计算出A写下的是什么数字了。

加密传输:A写下的是123 (明文),并且A计算出123 * 91 (B的公钥加密)等于11193,将结果的末三位193(加密结果)传给B;

解密过程:B把接收的193再乘以11 (B的私钥),193 * 11(解密过程) = 2123 末三位123(明文)就是A需要传输的内容;

原理:91乘以11等于1001,而任何一个三位数乘以1001后,末三位显然都不变。

使用Python实现TLS认证

生成证书脚本

# Generate the certificates and keys for testing.
​
​
PROJECT_NAME="www.test.com"
​
# Generate the openssl configuration files.
cat > ca_cert.conf << EOF
[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no
​
[ req_distinguished_name ]
 O                      = $PROJECT_NAME Dodgy Certificate Authority
EOF
​
cat > server_cert.conf << EOF
[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no
​
[ req_distinguished_name ]
 O                      = $PROJECT_NAME
 CN                     = 127.0.0.1
EOF
​
cat > client_cert.conf << EOF
[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no
​
[ req_distinguished_name ]
 O                      = $PROJECT_NAME Device Certificate
 CN                     = 127.0.0.1
EOF
​
mkdir ca
mkdir server
mkdir client
mkdir certDER
​
# private key generation
openssl genrsa -out ca.key 2048
openssl genrsa -out server.key 2048
openssl genrsa -out client.key 2048
​
# cert requests
openssl req -out ca.req -key ca.key -new -config ./ca_cert.conf
openssl req -out server.req -key server.key -new -config ./server_cert.conf
openssl req -out client.req -key client.key -new -config ./client_cert.conf
​
# generate the actual certs.
openssl x509 -req -in ca.req -out ca.crt -sha1 -days 5000 -signkey ca.key
openssl x509 -req -in server.req -out server.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
openssl x509 -req -in client.req -out client.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
​
openssl x509 -in ca.crt -outform DER -out ca.der
openssl x509 -in server.crt -outform DER -out server.der
openssl x509 -in client.crt -outform DER -out client.der
​
mv ca.crt ca.key ca/
mv server.crt server.key server/
mv client.crt client.key client/
​
mv ca.der server.der client.der certDER/
​
rm *.conf
rm *.req
rm *.srl

服务端脚本

# 此代码仅用于分析握手阶段,后续异常请忽略
import socket
import ssl
​
def run_server():
    # context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context = ssl.SSLContext(ssl.PROTOCOL_TLS)
    context.set_ciphers('ALL:@SECLEVEL=0')
    # context.options = ssl.OP_NO_TLSv1_2
    
    context.load_cert_chain(certfile="server/server.crt", keyfile="server/server.key")
    context.load_verify_locations(cafile="ca/ca.crt")
    context.verify_mode = ssl.CERT_REQUIRED
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
        sock.bind(("127.0.0.1", 1443))
        sock.listen(5)
        with context.wrap_socket(sock, server_side=True) as ssock:
            conn, addr = ssock.accept()
            with conn:
                print("Connected by", addr)
                while True:
                    data = conn.recv(1024)
                    if not data:
                        break
                    conn.sendall(data)
​
if __name__ == '__main__':
    run_server()
​
​

客户端脚本

import socket
import ssl
​
​
def connect_to_server():
    hostname = '127.0.0.1'
    # context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    # context = ssl.SSLContext(ssl.PROTOCOL_TLS)
    # context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)
    context.set_ciphers('ALL:@SECLEVEL=0')
    context.check_hostname = False
    context.load_cert_chain(certfile="client/client.crt", keyfile="client/client.key")
    context.load_verify_locations("ca/ca.crt")
    context.verify_mode = ssl.CERT_REQUIRED
    with socket.create_connection((hostname, 1443)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            print(ssock.version())
​
​
if __name__ == '__main__':
    connect_to_server()

使用urllib3的客户端脚本

import ssl
import urllib3
from requests.packages.urllib3.util import ssl_
​
def urllib_req():
    SSL_OPTIONS = ssl.OP_NO_TLSv1_1
   
    ctx = ssl_.create_urllib3_context(ssl.PROTOCOL_TLS)
    ctx.options |= SSL_OPTIONS
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
    ctx.set_ciphers('ALL:@SECLEVEL=0')
    ctx.check_hostname = False
    ctx.load_cert_chain(certfile="client/client.crt", keyfile="client/client.key")
    ctx.load_verify_locations("ca/ca.crt")
    http = urllib3.PoolManager(
        num_pools=1,
        maxsize=1,
        block=1,
        ssl_context=ctx,
    )
    resp = http.request('GET', 'https://127.0.0.1:1443')
    print(resp.status)
​
​
if __name__ == '__main__':
    urllib_req()

实验过程

  1. 服务端使用 ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    在该模式下,启用的是 TLSv1.2TLSv1.3两个安全的版本,客户端使用 TLSv1.2TLSv1.3连接都能成功,但是使用 TLSv1.1TLSv1.0版本会连接失败。失败时候会抛出如下异常。
    ssl.SSLError: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version
    
  2. 服务端使用 ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)方式,即指定使用 TLSv1.1版本。客户端只能使用 TLSv1.1版本来连接,其他连接都会失败。

实验结论

通过上面实验,可以得知的结论是,如果服务端开始允许的TLS版本覆盖比较全,在后续迭代过程中,因为安全的考虑,禁用了某些版本TLS,将会直接导致采用被禁用版本TLS的客户端无法与服务端直接通信。且该过程发生在握手时候的认证过程。

0

评论区