Azure IoT Hub Python SDK X509证书密码校验未生效问题
问题背景
我最初参考微软官方PowerShell版本的X.509证书配置教程搭建测试环境,先后验证了两个设备连接Azure IoT Hub的场景:
- 场景1:Windows 10笔记本上通过.NET Framework应用连接
测试代码如下:
该场景验证结果符合预期:传入正确PFX证书和匹配密码时程序正常运行,传入错误密码或无效PFX文件时程序直接运行失败。static void Main(string[] args) { try { // 创建X.509证书对象 var cert = new X509Certificate2(@"..\test-device-auth\test-device-auth.pfx", "pass", X509KeyStorageFlags.UserKeySet); Console.WriteLine("cert: "); Console.WriteLine(cert); // 使用X.509证书创建认证对象 var auth = new DeviceAuthenticationWithX509Certificate(deviceId, cert); // 创建设备客户端 var deviceClient = DeviceClient.Create("Arduino-IoT-Hub-Temperature.azure-devices.net", auth, TransportType.Mqtt); if (deviceClient == null) { Console.WriteLine("Failed to create DeviceClient!"); } else { Console.WriteLine("Successfully created DeviceClient!"); SendEvent(deviceClient).Wait(); } Console.WriteLine("Exiting...\n"); } catch (Exception ex) { Console.WriteLine("Error in sample: {0}", ex.Message); } } - 场景2:树莓派3B上通过Python脚本连接
测试代码如下:
该场景出现异常现象:使用的PEM格式证书私钥没有被# ------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- import os import uuid from azure.iot.device.aio import IoTHubDeviceClient from azure.iot.device import Message, X509 import asyncio messages_to_send = 10 async def main(): hostname = "Arduino-IoT-Hub-Temperature.azure-devices.net" # 门户上通过X509 CA签名或自签名能力创建的设备ID device_id = "test-device-auth" x509 = X509( cert_file="../test-device-auth/test-device-auth-public.pem", key_file="../test-device-auth/test-device-auth-private.pem", pass_phrase="pass", ) # 客户端对象用于和Azure IoT Hub交互 device_client = IoTHubDeviceClient.create_from_x509_certificate( hostname=hostname, device_id=device_id, x509=x509 ) # 建立客户端连接 await device_client.connect() async def send_test_message(i): print("sending message #" + str(i)) msg = Message("test wind speed " + str(i)) msg.message_id = uuid.uuid4() msg.correlation_id = "correlation-1234" # msg.custom_properties["tornado-warning"] = "yes" msg.content_encoding = "utf-8" msg.content_type = "application/json" await device_client.send_message(msg) print("done sending message #" + str(i)) # 并行发送指定数量的测试消息 await asyncio.gather(*[send_test_message(i) for i in range(1, messages_to_send + 1)]) # 关闭客户端 await device_client.shutdown() if __name__ == "__main__": asyncio.run(main()) # Python 3.6版本请用以下代码替换asyncio.run(main()) # loop = asyncio.get_event_loop() # loop.run_until_complete(main()) # loop.close()pass_phrase参数校验,无论传入正确密码、错误密码,甚至完全不传密码,程序都能正常运行。
问题原因
出现该现象的核心原因是当前使用的私钥PEM文件本身没有被密码加密,pass_phrase参数仅在私钥本身是加密状态时才会生效。
从PFX格式导出PEM公私钥的时候,如果没有给私钥设置加密密码,导出的私钥文件是明文存储的,此时底层TLS库读取私钥时根本不需要密码,传入的pass_phrase参数会被直接忽略,自然不会触发密码校验逻辑。
.NET场景下PFX文件本身是带密码加密的容器格式,加载时必须输入正确密码才能读取到内部的公私钥内容,所以密码校验逻辑正常生效。
修复配置方法
要让Python场景下的私钥密码校验正常生效,只需要导出私钥PEM文件时给私钥设置AES加密密码即可,操作步骤如下:
- 如果使用OpenSSL从PFX导出PEM,执行导出私钥的命令时,去掉
-nodes参数(该参数的作用就是导出不加密的明文私钥),按照终端提示设置要使用的私钥密码即可,示例命令:# 导出加密的私钥文件,执行后按提示输入密码 openssl pkcs12 -in test-device-auth.pfx -nocerts -out test-device-auth-private.pem # 导出公钥证书文件(不需要加密) openssl pkcs12 -in test-device-auth.pfx -clcerts -nokeys -out test-device-auth-public.pem - 验证私钥文件是否加密:打开导出的
test-device-auth-private.pem文件,如果头部是-----BEGIN ENCRYPTED PRIVATE KEY-----就说明私钥已经被加密,此时如果传入的pass_phrase和设置的私钥密码不匹配,程序会直接抛出私钥读取失败的错误,密码校验机制就会正常生效;如果头部是-----BEGIN PRIVATE KEY-----说明还是明文私钥,需要重新导出。
注意:Azure IoT Python SDK的X509类底层依赖系统安装的OpenSSL库加载证书,只要私钥本身是加密格式,传入的密码错误时会直接在证书加载阶段抛出异常,不会建立连接。
内容的提问来源于stack exchange,提问作者VitoCK
相关产品推荐
相关产品推荐

