本文将介绍密钥认证设备如何使用 DeviceSecret 建立 TLS-PSK 连接,并通过8883端口完成设备身份和 MQTT 接入。
方案概述
TLS-PSK 使用设备与平台预先共享的 DeviceSecret 完成 TLS 握手和设备身份认证。
设备将 Base64 编码的 DeviceSecret 解码为原始二进制密钥,并将其作为 TLS 预共享密钥;平台根据 PSK Identity 识别设备,并使用平台保存的 DeviceSecret 验证握手结果。
安全层面 | 实现方式 | 作用 |
设备身份识别 | PSK Identity | 标识 ProductID 和 DeviceName。 |
设备身份认证 | TLS 预共享密钥 | 验证设备是否持有正确的 DeviceSecret。 |
平台身份确认 | TLS-PSK 握手 | 验证对端是否持有相同预共享密钥。 |
传输链路保护 | TLS 1.2 | 对设备与平台之间的数据进行加密和完整性保护。 |
TLS-PSK 不使用 X.509证书。平台在握手过程中不提供服务器证书,设备不需要加载平台 CA 证书、设备证书或设备私钥。
说明:
本文中的 TLS-PSK 仅指设备端 SDK 支持的预共享密钥接入方式,不等同于“证书型 TLS 加 MQTT HMAC 认证”。
适用场景
以下场景可以使用 TLS-PSK 接入:
使用支持 TLS-PSK 的物联网开发平台设备端 SDK。
设备采用 DeviceSecret 进行身份认证。
需要通过8883端口建立加密连接。
能够为每台设备安全保存独立 DeviceSecret。
普通 MQTT 客户端不一定支持 TLS-PSK。使用自研客户端时,需要确认 TLS 库支持 PSK 回调和平台要求的密码套件。
如果业务要求使用 PKI 体系、设备证书或硬件私钥,应选择设备证书认证。
认证流程
TLS-PSK 接入流程参见下图:

TLS-PSK 握手成功后:
TLS 连接使用预共享密钥完成双端身份确认。
平台不会发送服务器证书。
设备不执行 CA 证书链和服务器域名校验。
MQTT CONNECT 报文不设置 Password 字段。
MQTT 连接成功后,设备状态变为在线。
方案区别
对比项 | 1883:MQTT 密钥认证 | 8883:TLS-PSK 密钥认证 |
传输加密 | 不使用 TLS。 | 使用 TLS 1.2。 |
DeviceSecret 用途 | 生成 MQTT HMAC Password。 | 作为 TLS 预共享密钥。 |
身份认证层级 | MQTT 层。 | TLS 握手层。 |
PSK Identity | 不涉及。 | ProductID 与 DeviceName 拼接。 |
MQTT Password | 需要。 | 不设置。 |
服务器证书 | 不涉及。 | 不提供。 |
平台 CA 证书 | 不需要。 | 不需要。 |
客户端要求 | 支持 MQTT 3.1.1。 | 同时支持 MQTT 3.1.1和 TLS-PSK。 |
适用场景 | 受控环境或基础验证。 | 设备端 SDK 加密接入。 |
两种方式使用同一个 DeviceSecret,但认证位置和参数用途不同。TLS-PSK 不是在1883连接参数基础上简单修改端口。
MQTT 连接参数
参数 | 取值或生成方式 | 说明 |
Broker Address | 以控制台显示为准。 | MQTT 服务器地址。 |
Broker Port | 8883 | TLS-PSK 接入端口。 |
TLS 版本 | TLS 1.2 | 当前验证使用的协议版本。 |
密码套件 | PSK-AES128-CBC-SHA | 当前 SDK 兼容路径验证结果。 |
PSK Identity | ${ProductID}${DeviceName} | ProductID 与 DeviceName 直接拼接。 |
PSK Key | Base64解码后的 DeviceSecret。 | TLS 预共享密钥。 |
MQTT 版本 | MQTT 3.1.1 | MQTT 协议版本。 |
MQTT Client ID | ${ProductID}${DeviceName} | MQTT 客户端标识。 |
MQTT Username | 按设备端 SDK 规则生成。 | 用于 MQTT 连接信息。 |
MQTT Password | 不设置。 | 不应提交 HMAC Password。 |
KeepAlive | 0~900秒 | 根据设备网络环境设置。 |
说明:
${}表示变量,不是实际拼接字符。连接域名应以设备所属地域及控制台显示为准。“Password 不设置”表示 MQTT CONNECT 报文中不启用 Password 标志,不建议用空字符串代替未设置。
使用 Python 连接平台
本节使用 Python 脚本连接物联网开发平台。当脚本返回认证成功且控制台设备状态显示为在线时,即表示设备密钥认证接入完成。
本示例仅验证设备认证和上线,不包含 Topic 发布、订阅及物模型消息通信。
前提条件
开始接入前,请确认已完成以下准备:
已创建认证方式为密钥认证的产品。
已在产品下创建设备。
已获得设备的 MQTT 连接参数。
本地环境已安装 Python 3.13 或以上版本。
当前 Python 的 OpenSSL 支持 TLS-PSK。
本地网络能够访问平台 MQTT 接入域名的 8883 端口。
获取设备身份参数
控制台字段 | 脚本配置项 | 说明 |
产品 ID | ProductID | 标识设备所属产品。 |
设备名称 | DeviceName | 标识产品下的具体设备。 |
设备密钥 | DeviceSecret | 用于生成 TLS-PSK 预共享密钥。 |
MQTT 服务器地址 | Broker Address | 设备所属地域的 MQTT 接入域名。 |
准备运行环境
建议在虚拟环境中安装依赖和运行代码。
Python 标准库从 Python 3.13开始提供 TLS-PSK 客户端回调能力。
运行前检查:
python.exe -c "import ssl; print(ssl.OPENSSL_VERSION); print('HAS_PSK =', ssl.HAS_PSK)"
只有输出以下结果时才能运行 TLS-PSK 验证代码:
HAS_PSK = True
创建连接脚本
将以下代码保存为
mqtt_tls_psk_connect.py:注意:
该代码仅用于验证平台 TLS-PSK 路径,不建议直接作为生产设备实现。生产设备应使用经过适配和验证的设备端 SDK。
#!/usr/bin/env python3# -*- coding: utf-8 -*-import base64import binasciiimport getpassimport secretsimport socketimport sslimport stringimport structimport sysimport timeMIN_PYTHON_VERSION = (3, 13)DEFAULT_PORT = 8883DEFAULT_KEEPALIVE = 60DEFAULT_TIMEOUT = 8DEFAULT_EXPIRY_SECONDS = 3600SDK_APP_ID = "12010126"TLS_PSK_CIPHER = "PSK-AES128-CBC-SHA"def check_runtime():"""在读取设备凭证前检查TLS-PSK运行环境。"""print("正在检查运行环境。")print(f"Python版本:{sys.version.split()[0]}")print(f"Python路径:{sys.executable}")print(f"OpenSSL版本:{ssl.OPENSSL_VERSION}")if sys.version_info < MIN_PYTHON_VERSION:raise RuntimeError("当前Python版本不支持TLS-PSK客户端接口。\\n""请使用Python 3.13及以上版本运行本程序。\\n""例如:D:\\\\tools\\\\python313\\\\python.exe ""mqtt_tls_psk_connect.py")if not getattr(ssl, "HAS_PSK", False):raise RuntimeError("当前Python所使用的OpenSSL未启用TLS-PSK能力。")if not hasattr(ssl.SSLContext, "set_psk_client_callback"):raise RuntimeError("当前ssl模块缺少set_psk_client_callback接口。")print("TLS-PSK能力:支持")print("运行环境检查通过。\\n")def required_input(prompt):"""读取必填参数并清除首尾空格。"""value = input(prompt).strip()if not value:raise ValueError("输入内容不能为空")return valuedef get_port():"""读取并校验MQTT端口。"""value = input(f"MQTT服务器端口(直接回车使用{DEFAULT_PORT}):").strip()if not value:return DEFAULT_PORTtry:port = int(value)except ValueError as exc:raise ValueError("端口号必须是整数") from excif not 1 <= port <= 65535:raise ValueError("端口号必须在1~65535范围内")return portdef decode_device_secret(device_secret):"""将Base64格式的DeviceSecret解码为TLS PSK。"""try:psk = base64.b64decode(device_secret,validate=True,)except (binascii.Error, ValueError) as exc:raise ValueError("DeviceSecret不是有效的Base64字符串,""请检查是否完整复制了控制台中的设备密钥") from excif not psk:raise ValueError("DeviceSecret解码结果为空")return pskdef random_connid(length=5):"""生成MQTT Username使用的随机connid。"""alphabet = string.ascii_letters + string.digitsreturn "".join(secrets.choice(alphabet)for _ in range(length))def encode_utf8(value):"""编码MQTT UTF-8字符串。"""data = value.encode("utf-8")if len(data) > 65535:raise ValueError("MQTT字符串长度超过65535字节")return struct.pack("!H", len(data)) + datadef encode_remaining_length(length):"""编码MQTT Remaining Length字段。"""result = bytearray()while True:encoded = length % 128length //= 128if length > 0:encoded |= 0x80result.append(encoded)if length == 0:breakreturn bytes(result)def build_connect_packet(client_id, username, keepalive):"""构造MQTT 3.1.1 CONNECT报文。TLS-PSK模式下:- MQTT Username仍用于标识设备;- MQTT Password不填写;- DeviceSecret在TLS握手层作为PSK使用。"""connect_flags = 0x82# 0x80:包含Username# 0x02:Clean Session# 不设置Password标志位variable_header = (encode_utf8("MQTT")+ bytes([4])+ bytes([connect_flags])+ struct.pack("!H", keepalive))payload = (encode_utf8(client_id)+ encode_utf8(username))remaining_length = len(variable_header) + len(payload)return (bytes([0x10])+ encode_remaining_length(remaining_length)+ variable_header+ payload)def recv_exact(sock, length):"""从Socket读取指定长度的数据。"""chunks = bytearray()while len(chunks) < length:data = sock.recv(length - len(chunks))if not data:raise ConnectionError("服务器已关闭连接")chunks.extend(data)return bytes(chunks)def receive_mqtt_packet(sock):"""接收一个完整MQTT报文。"""first_byte = recv_exact(sock, 1)[0]multiplier = 1remaining_length = 0while True:encoded = recv_exact(sock, 1)[0]remaining_length += (encoded & 0x7F) * multiplierif encoded & 0x80 == 0:breakmultiplier *= 128if multiplier > 128 * 128 * 128:raise ValueError("MQTT Remaining Length格式错误")payload = recv_exact(sock, remaining_length)return first_byte, payloaddef parse_connack(first_byte, payload):"""解析MQTT 3.1.1 CONNACK报文。"""if first_byte != 0x20 or len(payload) != 2:raise ValueError("服务器返回的不是有效CONNACK报文")return_code = payload[1]messages = {0: "连接成功",1: "不支持的协议版本",2: "ClientId不合法",3: "服务不可用",4: "Username或认证信息错误",5: "未授权",}return return_code, messages.get(return_code,f"未知返回码:{return_code}",)def create_tls_psk_context(psk_identity, psk):"""创建TLS-PSK客户端上下文。"""context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)# TLS-PSK路径不使用X.509服务器证书。context.check_hostname = Falsecontext.verify_mode = ssl.CERT_NONE# 当前已验证的平台TLS-PSK路径使用TLS 1.2。context.minimum_version = ssl.TLSVersion.TLSv1_2context.maximum_version = ssl.TLSVersion.TLSv1_2try:context.set_ciphers(TLS_PSK_CIPHER)except ssl.SSLError as exc:raise RuntimeError(f"当前OpenSSL不支持密码套件:{TLS_PSK_CIPHER}") from excdef psk_callback(identity_hint):return psk_identity, pskcontext.set_psk_client_callback(psk_callback)return contextdef main():# 必须先检查运行环境,再读取设备信息和凭证。check_runtime()print("密钥认证设备TLS-PSK连接验证工具")print("本工具仅验证设备认证和上线。")print("输入的DeviceSecret仅保留在程序内存中,""不会显示或写入文件。\\n")product_id = required_input("ProductID:")device_name = required_input("DeviceName:")default_broker = (f"{product_id}.iotcloud.tencentdevices.com")broker = input("MQTT服务器地址"f"(直接回车使用{default_broker}):").strip() or default_brokerif any(char in broker for char in "${}/"):raise ValueError("MQTT服务器地址应为不包含协议和路径的完整域名")port = get_port()device_secret = getpass.getpass("DeviceSecret(输入内容不会显示):").strip()if not device_secret:raise ValueError("DeviceSecret不能为空")psk = decode_device_secret(device_secret)client_id = f"{product_id}{device_name}"psk_identity = client_idconnid = random_connid()expiry = int(time.time()) + DEFAULT_EXPIRY_SECONDSusername = (f"{client_id};{SDK_APP_ID};{connid};{expiry}")print("\\n连接配置:")print(f"MQTT服务器:{broker}:{port}")print(f"ClientId:{client_id}")print(f"PSK Identity:{psk_identity}")print(f"PSK长度:{len(psk)}字节")print(f"TLS密码套件:{TLS_PSK_CIPHER}")print("MQTT Username:已自动生成")print("MQTT Password:不使用")print("DeviceSecret不会输出。")confirm = input("\\n是否开始TLS-PSK连接?[y/N]:").strip().lower()if confirm != "y":print("已取消连接。")return 0context = create_tls_psk_context(psk_identity,psk,)raw_socket = Nonetls_socket = Nonetry:print(f"\\n正在连接:{broker}:{port}")raw_socket = socket.create_connection((broker, port),timeout=DEFAULT_TIMEOUT,)tls_socket = context.wrap_socket(raw_socket,server_hostname=broker,)print("TLS-PSK握手成功。")print(f"TLS版本:{tls_socket.version()}")cipher = tls_socket.cipher()if cipher:print(f"协商密码套件:{cipher[0]}")connect_packet = build_connect_packet(client_id,username,DEFAULT_KEEPALIVE,)tls_socket.sendall(connect_packet)first_byte, payload = receive_mqtt_packet(tls_socket)return_code, message = parse_connack(first_byte,payload,)if return_code != 0:raise RuntimeError(f"MQTT认证失败:{message}")print("\\nTLS-PSK连接及MQTT认证成功。")print("设备已连接物联网开发平台。")print("请前往控制台查看设备在线状态。")print("程序将保持连接,按Ctrl+C退出。")# 每30秒发送一次MQTT PINGREQ,保持设备在线。tls_socket.settimeout(40)while True:time.sleep(30)tls_socket.sendall(b"\\xC0\\x00")packet_type, packet_payload = (receive_mqtt_packet(tls_socket))if packet_type != 0xD0:raise RuntimeError("未收到预期的MQTT PINGRESP")finally:if tls_socket is not None:try:# MQTT DISCONNECTtls_socket.sendall(b"\\xE0\\x00")except Exception:passtry:tls_socket.close()except Exception:passelif raw_socket is not None:try:raw_socket.close()except Exception:passreturn 0if __name__ == "__main__":try:sys.exit(main())except KeyboardInterrupt:print("\\n用户终止连接。")sys.exit(0)except socket.gaierror:print("\\n运行失败:无法解析MQTT服务器地址。")sys.exit(1)except socket.timeout:print("\\n运行失败:连接或响应超时。")sys.exit(1)except ssl.SSLError as exc:print(f"\\nTLS-PSK连接失败:{exc}")sys.exit(1)except Exception as exc:print(f"\\n运行失败:{exc}")sys.exit(1)
运行连接脚本
执行:
# Windows PowerShellpython mqtt_tls_psk_connect.py# Linux && macOS Shellpython3 mqtt_tls_psk_connect.py
# 运行成功后依次输入ProductID:DeviceName:MQTT服务器地址:MQTT服务器端口(直接回车使用8883):DeviceSecret(输入内容不会显示):
连接成功时,程序输出类似:
是否开始TLS-PSK连接?[y/N]:y正在连接:******.iotcloud.tencentdevices.com:8883TLS-PSK握手成功。TLS版本:TLSv1.2协商密码套件:TLS-PSK连接及MQTT认证成功。设备已连接物联网开发平台。请前往控制台查看设备在线状态。程序将保持连接,按Ctrl+C退出。
程序会持续运行并保持 MQTT 连接。需要断开连接时,按下
Ctrl+C。验证设备上线
问题排查
无法连接8883端口
检查:
MQTT 服务器地址是否正确。
是否错误保留
${}。防火墙是否允许访问8883端口。
DNS 是否能够解析服务器域名。
产品和设备是否属于当前地域。
TLS-PSK 握手失败
检查:
客户端 TLS 库是否支持 PSK。
密码套件是否受支持。
PSK Identity 是否为 ProductID 与 DeviceName 直接拼接。
DeviceSecret 是否属于当前设备。
DeviceSecret 是否完成 Base64解码。
PSK Key 是否以二进制形式传入。
TLS 握手成功但 MQTT 连接失败
检查:
MQTT 协议是否为3.1.1。
Client ID 是否正确。
Username 是否按 SDK 规则生成。
MQTT CONNECT 是否错误设置 Password。
Client ID 和 PSK Identity 是否指向同一设备。
设备是否已被禁用或删除。