You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python asyncio连接升级TLS时的顺序问题及解决咨询

无需自定义asyncio Protocol的MySQL协议TLS中途升级方案

针对你遇到的客户端先发起TLS请求时握手失败的问题,无需自定义asyncio.Protocol,直接用Python 3.7+内置的asyncio.start_tls()即可解决,核心是严格遵循MySQL有线协议的TLS升级顺序:客户端发送SSL请求→服务器返回允许应答→双向TLS握手。

关键解决步骤

  1. 服务器先读取客户端的SSL请求标识(MySQL协议中为特定格式的包,此处简化为1字节标识)
  2. 服务器返回允许SSL的应答包
  3. 调用asyncio.start_tls()将现有TCP流升级为TLS加密流
  4. 使用升级后的流进行后续加密通信

完整代码示例

服务器端代码

import asyncio
import ssl

async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    # 读取客户端的SSL请求(模拟MySQL的SSL请求标识)
    try:
        ssl_request = await reader.readexactly(1)
    except asyncio.IncompleteReadError:
        writer.close()
        await writer.wait_closed()
        return

    if ssl_request == b'\x01':
        # 发送允许SSL的应答(模拟MySQL的同意标识)
        writer.write(b'\x00')
        await writer.drain()

        # 初始化服务器SSL上下文
        ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        ssl_context.load_cert_chain(certfile="server.crt", keyfile="server.key")
        
        # 升级为TLS流,核心调用:asyncio.start_tls
        tls_reader, tls_writer = await asyncio.start_tls(
            reader,
            writer,
            ssl_context,
            server_side=True,
            ssl_handshake_timeout=10
        )

        # 加密通信逻辑
        try:
            data = await tls_reader.read(1024)
            print(f"收到加密数据: {data.decode('utf-8')}")
            tls_writer.write(b"已收到加密请求,这是加密响应")
            await tls_writer.drain()
        finally:
            tls_writer.close()
            await tls_writer.wait_closed()
    else:
        # 处理非SSL请求
        writer.write(b"无效请求,请先发起SSL升级")
        await writer.drain()
        writer.close()
        await writer.wait_closed()

async def main():
    server = await asyncio.start_server(handle_client, '127.0.0.1', 8888)
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

客户端代码(模拟MySQL先发起TLS请求的行为)

import asyncio
import ssl

async def main():
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
    # 发送SSL升级请求
    writer.write(b'\x01')
    await writer.drain()

    # 读取服务器的同意应答
    ssl_ack = await reader.readexactly(1)
    if ssl_ack == b'\x00':
        # 初始化客户端SSL上下文(测试环境禁用证书验证,生产环境需开启)
        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE
        
        # 升级为TLS流
        tls_reader, tls_writer = await asyncio.start_tls(
            reader,
            writer,
            ssl_context,
            server_side=False,
            server_hostname='127.0.0.1'
        )

        # 发送加密数据
        tls_writer.write(b"Hello MySQL 协议客户端(加密)")
        await tls_writer.drain()

        # 接收加密响应
        data = await tls_reader.read(1024)
        print(f"收到加密响应: {data.decode('utf-8')}")

        tls_writer.close()
        await tls_writer.wait_closed()

asyncio.run(main())

注意事项

  • 顺序严格性:必须先处理明文的SSL请求/应答,再触发TLS握手,否则会导致明文数据被当作TLS握手数据解析,引发握手失败
  • 证书准备:服务器端需要生成自签名证书(执行命令:openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes)
  • 流的替换:升级后必须使用start_tls()返回的tls_reader和tls_writer,原有的流对象已被TLS层接管,不可再使用
  • 超时设置:添加ssl_handshake_timeout避免因网络问题导致的握手阻塞

内容的提问来源于stack exchange,提问作者Barak Alon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 07:25:17