如何用Python ssl模块创建匿名DH TLS套接字并实现防凭证转发的安全认证
关于P2P协议中无证书TLS连接及挑战响应认证的解决方案
问题1:无证书TLS连接的实现
要在Python中创建无需证书的TLS连接,可采用**匿名Diffie-Hellman(ADH)**密码套件——这类套件仅通过DH交换会话密钥,不依赖证书完成身份验证,刚好匹配你的需求。
实现步骤
- 创建
SSLContext时指定兼容现代TLS的协议版本,同时禁用证书验证(因为无需证书)。 - 显式声明支持的ADH密码套件(部分默认套件列表可能不包含ADH)。
代码示例
服务器端
import socket import ssl # 创建服务器上下文,禁用证书验证 server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) server_context.check_hostname = False server_context.verify_mode = ssl.CERT_NONE # 指定ADH加密套件,这里选用AES256加密的版本 server_context.set_ciphers('ADH-AES256-SHA') # 绑定Socket并包装SSL层 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind(('localhost', 8443)) sock.listen(1) with server_context.wrap_socket(sock, server_side=True) as ssl_sock: conn, addr = ssl_sock.accept() print(f"已连接节点:{addr}") # 后续执行挑战响应认证
客户端
import socket import ssl # 创建客户端上下文,禁用证书验证 client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) client_context.check_hostname = False client_context.verify_mode = ssl.CERT_NONE client_context.set_ciphers('ADH-AES256-SHA') # 连接服务器并包装SSL层 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: with client_context.wrap_socket(sock, server_hostname='localhost') as ssl_sock: ssl_sock.connect(('localhost', 8443)) print("已连接服务器节点") # 后续执行挑战响应认证
注意:ADH套件本身不提供身份验证,必须依赖后续的挑战响应确认对方身份,否则中间人可轻松拦截会话。
问题2:防中间人转发的挑战响应认证
要排除中间人转发凭证的风险,挑战响应必须绑定当前TLS会话的唯一性信息,同时采用双向认证(双方都发起挑战)。核心思路是:挑战信息包含会话专属的随机值或会话派生数据,确保中间人无法复用之前的签名结果。
具体实现流程
- 每个节点预先生成ECDSA密钥对(私钥仅本地保存,公钥可在P2P网络中交换或预先共享)。
- TLS握手完成后,双方执行双向挑战:
- 节点A生成随机nonce,附加TLS会话主密钥的SHA256哈希值(或会话ID哈希),将组合数据发送给节点B。
- 节点B用自身ECDSA私钥对组合数据签名,将签名和公钥发送给节点A。
- 节点A用节点B的公钥验证签名,确认签名有效且哈希值匹配当前会话,完成对B的认证。
- 节点B重复上述流程,发起对节点A的挑战。
关键细节
- 加入会话主密钥哈希:中间人无法获取两端的会话主密钥,因此无法生成匹配当前会话的签名,即使拿到旧签名也无法复用。
- 双向认证:避免单向认证时,中间人冒充一方转发挑战的情况。
代码片段(ECDSA挑战响应)
import os import hashlib from cryptography.hazmat.primitives.asymmetric import ecdsa from cryptography.hazmat.primitives import serialization, hashes # 每个节点本地生成ECDSA密钥对 private_key = ecdsa.generate_private_key(ecdsa.SECP256R1()) public_key = private_key.public_key() def generate_challenge(ssl_sock): # 生成16字节随机nonce nonce = os.urandom(16) # 计算会话主密钥的SHA256哈希 session_hash = hashlib.sha256(ssl_sock.get_session().master_key).digest() # 组合挑战数据 return nonce, nonce + session_hash def sign_challenge(private_key, challenge): # ECDSA签名挑战数据 return private_key.sign(challenge, ecdsa.ECDSA(hashes.SHA256())) def verify_challenge(public_key, challenge, signature): # 验证签名有效性 try: public_key.verify(signature, challenge, ecdsa.ECDSA(hashes.SHA256())) return True except Exception: return False
执行时,双方互相发送挑战、签名,验证通过后即可确认身份,排除中间人攻击。
内容的提问来源于stack exchange,提问作者Antti Haapala
相关产品推荐
相关产品推荐

