Python 3.11与MicroPython 1.20 TLS通信失败:无共享密码套件
问题背景
我尝试在两台设备间传输文本:一台为Python 3.11服务器,另一台为MicroPython 1.20客户端。两台设备均有独立密钥,服务器持有服务器证书,密钥通过以下命令生成:
openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout server-key.pem -out server-cert.pem
按MicroPython文档要求,我将服务器证书转换为DER格式:
openssl.exe x509 -in server-cert.pem -out server-cert.der -outform DER
MicroPython客户端代码
# main.py -- put your code here! import usocket as socket import ussl as ssl # 服务器IP地址和端口 server_ip = '192.168.178.67' server_port = 2002 server_address = (server_ip, server_port) server_ca = "/flash/cert/server-cert-test.der" server_key = "/flash/cert/server-key.pem" # 建立与服务器的连接 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(server_address) # 建立SSL连接 ssl_context = ssl.wrap_socket(client_socket, cert=(server_ca, server_key)) # 发送数据 ssl_context.write("Hallo, Server!".encode('utf-8')) # 接收并解码响应 response = ssl_context.read(1024) print("服务器响应:", response.decode('utf-8')) # 关闭连接 ssl_context.close() client_socket.close()
Python 3.11服务器代码
from socket import socket from ssl import wrap_socket def main(): server_certfile = "server-cert.pem" server_keyfile = "server-key.pem" s = socket() s.bind(('192.168.178.67', 2002)) s.listen(5) wrap_socket(s.accept()[0], 'server-key.pem', 'server-cert.pem', True) if __name__ == "__main__": main()
报错信息
经Wireshark分析,MicroPython客户端发送Client Hello并支持TLS1.2,但双方无法协商出共用密码套件,报错如下:
Python端报错:
ssl.SSLError: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:992)
MicroPython端报错:
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Wireshark抓包数据
6768 11:49:56,791402 192.168.178.126 192.168.178.67 TCP 60 55778 → 2002 [SYN] Seq=0 Win=6400 Len=0 MSS=800 6769 11:49:56,791478 192.168.178.67 192.168.178.126 TCP 58 2002 → 55778 [SYN, ACK] Seq=0 Ack=1 Win=64800 Len=0 MSS=1460 6770 11:49:56,791810 192.168.178.126 192.168.178.67 TCP 60 55778 → 2002 [ACK] Seq=1 Ack=1 Win=6400 Len=0 6771 11:49:57,118967 192.168.178.126 192.168.178.67 TLSv1.2 176 Client Hello 6772 11:49:57,119175 192.168.178.67 192.168.178.126 TLSv1.2 61 Alert (Level: Fatal, Description: Handshake Failure) 6773 11:49:57,119284 192.168.178.67 192.168.178.126 TCP 54 2002 → 55778 [FIN, ACK] Seq=8 Ack=123 Win=64678 Len=0 6774 11:49:57,119367 192.168.178.126 192.168.178.67 TCP 60 55778 → 2002 [ACK] Seq=123 Ack=9 Win=6392 Len=0
问题询问
我已尝试更换证书和密钥、调整wrap_socket实现方式,但问题仍存在。请问是代码实现有误,还是目前Python与MicroPython无法实现安全通信?求可行实现方案。
补充:TLS握手Client Hello显示的支持密码套件见附图。
可行解决方案
1. 修正客户端SSL配置错误
MicroPython客户端中,ssl.wrap_socket的cert参数用于客户端证书认证,而验证服务器证书应使用ca_certs参数。当前代码错误地将服务器证书和客户端密钥传给了cert,导致配置逻辑混乱。修正后的客户端SSL配置:
# 仅验证服务器证书(单向认证) ssl_context = ssl.wrap_socket(client_socket, ca_certs=server_ca) # 如需双向认证(服务器验证客户端身份),需准备客户端专属证书和密钥 # client_cert = "/flash/cert/client-cert.pem" # client_key = "/flash/cert/client-key.pem" # ssl_context = ssl.wrap_socket(client_socket, ca_certs=server_ca, cert=(client_cert, client_key))
2. 服务器端显式指定兼容密码套件
Python标准库默认启用的密码套件与MicroPython(基于mbedTLS)支持的套件可能无重叠,需根据Client Hello中的套件列表,在服务器端显式启用双方兼容的套件。修正后的服务器代码:
from socket import socket import ssl def main(): server_certfile = "server-cert.pem" server_keyfile = "server-key.pem" s = socket() s.bind(('192.168.178.67', 2002)) s.listen(5) conn, addr = s.accept() # 创建TLS服务器上下文,指定兼容协议版本 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(certfile=server_certfile, keyfile=server_keyfile) # 根据MicroPython支持的套件调整,示例为常见兼容套件 ssl_context.set_ciphers('AES128-SHA256:ECDHE-RSA-AES128-GCM-SHA256') ssl_conn = ssl_context.wrap_socket(conn, server_side=True) # 处理通信逻辑 data = ssl_conn.recv(1024) print("收到客户端数据:", data.decode('utf-8')) ssl_conn.sendall("Hallo, Client!".encode('utf-8')) ssl_conn.close() conn.close() if __name__ == "__main__": main()
3. 确保证书密钥算法兼容
MicroPython的mbedTLS对1024位RSA密钥支持有限(部分环境已禁用),建议重新生成2048位RSA证书:
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server-key.pem -out server-cert.pem
4. 匹配Client Hello中的密码套件
查看Wireshark抓包中Client Hello的密码套件列表,将服务器端set_ciphers的参数调整为包含这些套件(注意OpenSSL与mbedTLS的套件名称对应关系,比如mbedTLS的TLS_RSA_WITH_AES_128_CBC_SHA256对应OpenSSL的AES128-SHA256)。
内容的提问来源于stack exchange,提问作者Philipp

