TLS握手失败:PyOpenSSL与OpenSSL/嵌入式设备通信异常排查
PyOpenSSL与原生OpenSSL/嵌入式设备TLS1.3 PSK通信失败排查与修复
问题场景
需要编写基于pytest的Python测试用例验证Windows PC与嵌入式目标板的TLS通信,先通过原生OpenSSL完成验证:
- 服务端执行:
openssl s_server -accept 49152 -nocert -psk 1a2b3c4d - 客户端执行:
openssl s_client -port 49152 -psk 1a2b3c4d -tls1_3
嵌入式设备作为TLS1.3客户端可正常连接该原生OpenSSL服务端,但用PyOpenSSL实现的服务端/客户端,与原生OpenSSL或嵌入式设备通信时均失败:客户端发送Client Hello后,服务端返回致命警报:
Alert Message
Level: Fatal (2)
Description: Illegal Parameter (47)
失败组合包括:
- Windows原生OpenSSL服务端 ↔ PyOpenSSL客户端
- Windows原生OpenSSL客户端 ↔ PyOpenSSL服务端
使用Python 3.10.2,PyOpenSSL服务端代码如下:
from OpenSSL import SSL from socket import socket, AF_INET, SOCK_STREAM from openssl_psk import patch_context patch_context() server_addr = '127.0.0.1' server_port = 49152 psk_value : str = '1a2b3c4d' PSK_MAP = {b'Client_identity': psk_value} if __name__ == "__main__": def server_callback(conn_2, client_identity): return PSK_MAP[client_identity] ctx = SSL.Context(SSL.TLS_METHOD) ctx.set_options(SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1) ctx.set_verify(SSL.VERIFY_NONE) ctx.use_psk_identity_hint('Client_identity') # seems not to be necessary in server ctx.set_psk_server_callback(server_callback) sock = socket(AF_INET, SOCK_STREAM) conn_1 = SSL.Connection(ctx, sock) conn_1.bind((server_addr, server_port)) ctx.set_cipher_list(b"AES128-GCM-SHA256") conn_1.listen(1) while True: print('\tWaiting for client to connect...\r\n') newsocket, fromaddr = conn_1.accept() print ('Server accepted a socket-request') conn_2 = SSL.Connection(ctx, newsocket) conn_2.set_accept_state() conn_2.do_handshake() # bla bla
问题根源
- PSK值格式不匹配:原生OpenSSL的
-psk参数接受十六进制字符串,会自动解码为字节流;但代码中直接返回字符串'1a2b3c4d',而非对应的字节值,导致PSK验证失败。 - TLS版本配置模糊:
SSL.TLS_METHOD默认包含多版本协议,即使禁用了TLS1.0/1.1,仍可能存在协商歧义,需明确指定TLS1.3专用的上下文方法。 - 密码套件配置时机错误:在绑定套接字后才调用
ctx.set_cipher_list,此时上下文配置已生效,修改不会作用到后续连接。另外,TLS1.3的密码套件命名格式与旧版本不同(需前缀TLS_)。 - 参数类型错误:
use_psk_identity_hint需传入字节类型参数,代码中传入字符串会导致编码不兼容。
修复后的服务端代码
from OpenSSL import SSL from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR from openssl_psk import patch_context patch_context() server_addr = '127.0.0.1' server_port = 49152 # 将十六进制PSK字符串转换为字节流 psk_value = bytes.fromhex('1a2b3c4d') PSK_MAP = {b'Client_identity': psk_value} if __name__ == "__main__": def server_callback(conn, client_identity): # 增加容错,避免未知身份导致崩溃 return PSK_MAP.get(client_identity, b'') # 明确使用TLS1.3服务器专用上下文 ctx = SSL.Context(SSL.TLS_SERVER_METHOD) # 禁用所有旧版本TLS协议,仅保留TLS1.3 ctx.set_options(SSL.OP_NO_TLSv1 | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1_2) ctx.set_verify(SSL.VERIFY_NONE) # 传入字节类型的身份提示 ctx.use_psk_identity_hint(b'Client_identity') ctx.set_psk_server_callback(server_callback) # 在创建套接字前设置TLS1.3兼容的密码套件 ctx.set_cipher_list(b"TLS_AES_128_GCM_SHA256") sock = socket(AF_INET, SOCK_STREAM) # 开启地址复用,避免端口占用问题 sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) conn_1 = SSL.Connection(ctx, sock) conn_1.bind((server_addr, server_port)) conn_1.listen(1) while True: print('\tWaiting for client to connect...\r\n') newsocket, fromaddr = conn_1.accept() print(f'Server accepted connection from {fromaddr}') conn_2 = SSL.Connection(ctx, newsocket) conn_2.set_accept_state() try: conn_2.do_handshake() print("TLS handshake successful!") # 后续通信逻辑 except SSL.Error as e: print(f"Handshake failed: {e}") conn_2.shutdown() conn_2.close()
额外提示
- 客户端代码需同步修改:使用
SSL.TLS_CLIENT_METHOD,将PSK值转为字节,禁用旧协议,配置正确的TLS1.3密码套件。 - 确保PyOpenSSL依赖的OpenSSL版本与你编译的原生OpenSSL版本一致,版本差异可能导致协议协商失败。
- 嵌入式设备的PSK配置需与服务端完全匹配:身份标识为
Client_identity,PSK值为十六进制1a2b3c4d对应的字节流。
内容的提问来源于stack exchange,提问作者jcb6626
相关产品推荐
相关产品推荐

