你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

如何在不使用 SDK 的情况下通过 HTTPS 使用对称密钥

在本操作指南文章中,你将在不使用 Azure IoT DPS 设备 SDK 的情况下,通过 HTTPS 使用对称密钥来预配设备。 大多数语言提供用于发送 HTTP 请求的库,但在本文中,你将使用 cURL 命令行工具通过 HTTPS 发送和接收请求,而不是专注于特定的语言。

可以在 Linux 或 Windows 计算机上执行本文中的步骤。 如果在适用于 Linux 的 Windows 子系统 (WSL) 上运行或者在 Linux 计算机上运行,可以在本地系统上的 Bash 提示符下输入所有命令。 如果在 Windows 上运行,请在本地系统上的 GitBash 提示符下输入所有命令。

本文有多个路径,具体取决于你选择使用的注册条目类型。 安装必备组件后,请务必先阅读概述,然后继续。

先决条件

  • 如果没有 Azure 订阅,请在开始之前创建一个免费帐户

  • 完成通过 Azure 门户设置 IoT 中心设备预配服务中的步骤。

  • 确保计算机上安装了 Python 3.7 或更高版本。 可以通过运行 python --version 来检查 Python 版本。

  • 如果在 Windows 中运行,请安装最新版本的 Git。 确保将 Git 添加到可供命令窗口访问的环境变量。 请参阅软件自由保护组织提供的 Git 客户端工具,了解要安装的最新版 git 工具,其中包括 Git Bash,这是一个命令行应用,可以用来与本地 Git 存储库交互。 在 Windows 上,请在本地系统上的 GitBash 提示符下输入所有命令。

  • Azure CLI。 在本文中,有两个选项可用于运行 Azure CLI 命令:

    • 使用 Azure Cloud Shell,这是一个交互式 Shell,可在浏览器中运行 CLI 命令。 建议使用此选项,因为无需安装任何插件。 如果是首次使用 Cloud Shell,请登录到 Azure 门户。 按照 Cloud Shell 快速入门中的步骤启动 Cloud Shell 并选择 Bash 环境。
    • (可选)在本地计算机上运行 Azure CLI。 如果已安装 Azure CLI,请运行 az upgrade 以将 CLI 和扩展升级到当前版本。 要安装 Azure CLI,请参阅安装 Azure CLI
  • 如果在 Linux 或 WSL 环境中运行,请打开 Bash 提示符以在本地运行命令。 如果在 Windows 环境中运行,请打开 GitBash 提示符。

概述

对于本文,可以使用单独注册注册组通过 DPS 进行预配。

创建单独注册或注册组条目后,继续创建 SAS 令牌将设备注册到 DPS。

使用单独注册

如果你想要创建新的单独注册以用于本文,可以使用 az iot dps enrollment create 命令为对称密钥证明创建单独注册。

以下命令使用默认分配策略为 DPS 实例创建一个注册条目,并让 DPS 为设备分配主密钥和辅助密钥:

az iot dps enrollment create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --attestation-type symmetrickey
  • 替换资源组和 DPS 实例的名称。

  • 注册 ID 是设备的注册 ID。 注册 ID 是一个不区分大小写的字符串(最大长度为 128 个字符),包含字母数字字符和以下特殊字符:'-''.''_'':'。 最后一个字符必须是字母数字或短划线 ('-')。 确保命令中使用的注册 ID 符合此格式。

分配的对称密钥在响应的 attestation 属性中返回:


{
  "allocationPolicy": null,
  "attestation": {
    "symmetricKey": {
      "primaryKey": "G3vn0IZH9oK3d4wsxFpWBtd2KUrtjI+39dZVRf26To8w9OX0LaFV9yZ93ELXY7voqHEUsNhnb9bt717UP87KxA==",
      "secondaryKey": "4lNxgD3lUAOEOied5/xOocyiUSCAgS+4b9OvXLDi8ug46/CJzIn/3rN6Ys6gW8SMDDxMQDaMRnIoSd1HJ5qn/g=="
    },
    "tpm": null,
    "type": "symmetricKey",
    "x509": null
  },

  ...

}

记下单独注册条目的主密钥和注册 ID,因此稍后在本文中需要用到。

如果你要为本文使用现有的单独注册,可以使用 az iot dps enrollment show 命令获取主密钥:

az iot dps enrollment show -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --show-keys true

使用注册组

如果你想要创建新的注册组以用于本文,可以使用 az iot dps enrollment-group create 命令为对称密钥证明创建注册组。

以下命令使用默认分配策略为 DPS 实例创建一个注册组条目,并让 DPS 为注册组分配主密钥和辅助密钥:

az iot dps enrollment-group create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id}
  • 替换资源组和 DPS 实例的名称。

  • 注册 ID 是一个不区分大小写的字符串(最大长度为 128 个字符),包含字母数字字符和以下特殊字符:'-''.''_'':'。 最后一个字符必须是字母数字或短划线 ('-')。 它可以是你选择用于注册组的任何名称。

分配的对称密钥在响应的 attestation 属性中返回:


{
  "allocationPolicy": null,
  "attestation": {
    "symmetricKey": {
      "primaryKey": "G3vn0IZH9oK3d4wsxFpWBtd2KUrtjI+39dZVRf26To8w9OX0LaFV9yZ93ELXY7voqHEUsNhnb9bt717UP87KxA==",
      "secondaryKey": "4lNxgD3lUAOEOied5/xOocyiUSCAgS+4b9OvXLDi8ug46/CJzIn/3rN6Ys6gW8SMDDxMQDaMRnIoSd1HJ5qn/g=="
    },
    "tpm": null,
    "type": "symmetricKey",
    "x509": null
  },

  ...

}

记下主密钥。

如果你要为本文使用现有的单独注册,可以使用 az iot dps enrollment-group show 命令获取主密钥:

az iot dps enrollment-group show -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --show-keys true

派生一个设备密钥

在组注册中使用对称密钥证明时,不会直接使用注册组密钥。 你需要从注册组密钥为每个设备派生唯一密钥。 有关详细信息,请参阅采用对称密钥的组注册

在本部分,你将从注册组主密钥生成设备密钥,以计算设备的唯一注册 ID 的 HMAC-SHA256。 然后,结果将转换为 Base64 格式。

  1. 使用 openssl 生成唯一密钥。 你将使用以下 Bash shell 脚本。 请将 {primary-key} 替换为前面复制的注册组主密钥,并将 {contoso-simdevice} 替换为要用于设备的注册 ID。 注册 ID 是一个不区分大小写的字符串(最大长度为 128 个字符),包含字母数字字符和以下特殊字符:'-''.''_'':'。 最后一个字符必须是字母数字或短划线 ('-')。

    KEY={primary-key}
    REG_ID={contoso-simdevice}
    
    keybytes=$(echo $KEY | base64 --decode | xxd -p -u -c 1000)
    echo -n $REG_ID | openssl sha256 -mac HMAC -macopt hexkey:$keybytes -binary | base64
    
  2. 该脚本将输出类似于如下密钥:

    p3w2DQr9WqEGBLUSlFi1jPQ7UWQL4siAGy75HFTFbf8=
    

记下派生的设备密钥和用于生成该密钥的注册 ID,因为在下一部分需要用到。

还可以使用 Azure CLI 或 PowerShell 来派生设备密钥。 有关详细信息,请参阅派生设备密钥

创建 SAS 令牌

使用对称密钥证明时,设备将使用共享访问签名 (SAS) 令牌对 DPS 进行身份验证。 对于通过单独注册进行的设备预配,该令牌是使用注册条目中设置的主密钥或辅助密钥签名的。 对于通过注册组进行的设备预配,该令牌是使用派生的设备密钥签名的,而该密钥是使用注册组条目中设置的主密钥或辅助密钥生成的。 该令牌指定过期时间和目标资源 URI。

可以使用以下 Python 脚本生成 SAS 令牌:

from base64 import b64encode, b64decode
from hashlib import sha256
from time import time
from urllib.parse import quote_plus, urlencode
from hmac import HMAC

def generate_sas_token(uri, key, policy_name, expiry=3600):
     ttl = time() + expiry
     sign_key = "%s\n%d" % ((quote_plus(uri)), int(ttl))
     print(sign_key)
     signature = b64encode(HMAC(b64decode(key), sign_key.encode('utf-8'), sha256).digest())

     rawtoken = {
         'sr' :  uri,
         'sig': signature,
         'se' : str(int(ttl))
     }

     if policy_name is not None:
         rawtoken['skn'] = policy_name

     return 'SharedAccessSignature ' + urlencode(rawtoken)

uri = '[resource_uri]'
key = '[device_key]'
expiry = [expiry_in_seconds]
policy= '[policy]'

print(generate_sas_token(uri, key, policy, expiry))

其中:

  • [resource_uri] 是你尝试使用此令牌访问的资源的 URI。 对于 DPS,其格式为 [dps_id_scope]/registrations/[dps_registration_id],其中 [dps_id_scope] 是 DPS 实例的 ID 范围,[dps_registration_id] 是用于设备的注册 ID。

    可以从 Azure 门户中实例的“概述”窗格获取 DPS 实例的 ID 范围,或者可以使用 az iot dps show Azure CLI 命令(请将占位符替换为资源组和 DPS 实例的名称):

    az iot dps show -g {resource_group_name} --name {dps_name}
    
  • [device_key] 是与设备关联的设备密钥。 此密钥是在单独注册中为你指定或自动生成的密钥,或者是组注册的派生密钥。

    • 如果使用的是单独注册,请使用在使用单独注册中保存的主密钥。

    • 如果使用的是注册组,请使用在使用注册组中生成的派生设备密钥。

  • [expiry_in_seconds] 是此 SAS 令牌的有效期(以秒为单位)。

  • [policy] 是与设备密钥关联的策略。 对于 DPS 设备注册,策略将硬编码为“注册”。

名为 my-symkey-device 且有效期为 30 天的设备的一组示例输入如下所示。

uri = '0ne00111111/registrations/my-symkey-device'
key = '18RQk/hOPJR9EbsJlk2j8WA6vWaj/yi+oaYg7zmxfQNdOyMSu+SJ8O7TSlZhDJCYmn4rzEiVKIzNiVAWjLxrGA=='
expiry = 2592000
policy='registration'

修改设备和 DPS 实例的脚本并将其保存为 Python 文件;例如,generate_token.py。 运行脚本,例如 python generate_token.py。 它应输出如下所示的 SAS 令牌:

0ne00111111%2Fregistrations%2Fmy-symkey-device
1663952627
SharedAccessSignature sr=0ne00111111%2Fregistrations%2Fmy-symkey-device&sig=eNwg52xQdFTNf7bgPAlAJBCIcONivq%2Fck1lf3wtxI4A%3D&se=1663952627&skn=registration

复制并保存以 SharedAccessSignature 开头的整个行。 此行是 SAS 令牌。 在后续部分需要使用它。

若要详细了解如何将 SAS 令牌与 DPS 配合使用以及令牌的结构,请参阅使用 SAS 控制对 DPS 的访问

注册设备

调用注册设备 REST API 以通过 DPS 预配设备。

使用以下 curl 命令:

curl -L -i -X PUT -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: [sas_token]' -d '{"registrationId": "[registration_id]"}' https://global.azure-devices-provisioning.net/[dps_id_scope]/registrations/[registration_id]/register?api-version=2019-03-31

其中:

  • -L 告知 curl 遵循 HTTP 重定向。

  • –i 告知 curl 在输出中包含协议标头。 这些标头并非绝对必要,但它们可能很有用。

  • -X PUT 告知 curl 这是一个 HTTP PUT 命令。 对于此 API 调用是必需的。

  • -H 'Content-Type: application/json' 告知 DPS 我们正在发布 JSON 内容,其值必须是“application/json”。

  • -H 'Content-Encoding: utf-8' 告知 DPS 我们对消息正文使用的编码。 请根据你的操作系统/客户端设置适当的值;但是,值通常是 utf-8

  • -H 'Authorization: [sas_token]' 告知 DPS 使用你的 SAS 令牌进行身份验证。 请将 [sas_token] 替换为在创建 SAS 令牌中生成的令牌。

  • -d '{"registrationId": "[registration_id]"}'–d 参数是发布的消息的“数据”或正文。 它必须是 JSON,格式为 '{"registrationId":"[registration_id"}'。 请注意,对于 curl,需要将它括在单引号中;否则需要转义 JSON 中的双引号。

  • 最后,最后一个参数是要发布到的 URL。 对于“常规”(即非本地)DPS,将使用全局 DPS 终结点 global.azure-devices-provisioning.net:https://global.azure-devices-provisioning.net/[dps_id_scope]/registrations/[registration_id]/register?api-version=2019-03-31。 请注意,必须将 [dps_scope_id][registration_id] 替换为适当的值。

例如:

curl -L -i -X PUT -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: SharedAccessSignature sr=0ne00111111%2Fregistrations%2Fmy-symkey-device&sig=eNwg52xQdFTNf7bgPAlAJBCIcONivq%2Fck1lf3wtxI4A%3D&se=1663952627&skn=registration' -d '{"registrationId": "my-symkey-device"}' https://global.azure-devices-provisioning.net/0ne00111111/registrations/my-symkey-device/register?api-version=2021-06-01

成功的调用将返回类似于下面的响应:

HTTP/1.1 202 Accepted
Date: Wed, 31 Aug 2022 22:02:49 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Location: https://global.azure-devices-provisioning.net/0ne00111111/registrations/my-symkey-device/register
Retry-After: 3
x-ms-request-id: a021814f-0cf6-4ce9-a1e9-ead7eb5118d9
Strict-Transport-Security: max-age=31536000; includeSubDomains

{"operationId":"5.316aac5bdc130deb.b1e02da8-c3a0-4ff2-a121-7ea7a6b7f550","status":"assigning"}

响应包含操作 ID 和状态。 在本例中,状态设置为 assigning。 DPS 注册可能是一个长时间运行的操作,因此它以异步方式完成。 通常,你会使用操作状态查找 REST API 轮询状态,以确定何时分配了设备或是否发生了故障。

DPS 的有效状态值为:

  • assigned:状态调用的返回值将指示设备已分配到哪个 IoT 中心。

  • assigning:操作仍在运行。

  • disabled:DPS 中已禁用注册记录,因此无法分配设备。

  • failed:分配失败。 响应的 registrationState 记录中将返回 errorCodeerrorMessage,以指示哪个操作失败。

  • unassigned

若要调用“操作状态查找”API,请使用以下 curl 命令:

curl -L -i -X GET -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: [sas_token]' https://global.azure-devices-provisioning.net/[dps_id_scope]/registrations/[registration_id]/operations/[operation_id]?api-version=2019-03-31

使用在“注册设备”请求中所用的同一 ID 范围、注册 ID 和 SAS 令牌。 使用“注册设备”响应中返回的操作 ID。

例如:

curl -L -i -X GET -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: SharedAccessSignature sr=0ne00111111%2Fregistrations%2Fmy-symkey-device&sig=eNwg52xQdFTNf7bgPAlAJBCIcONivq%2Fck1lf3wtxI4A%3D&se=1663952627&skn=registration' https://global.azure-devices-provisioning.net/0ne00111111/registrations/my-symkey-device/operations/5.316aac5bdc130deb.f4f1828c-4dab-4ca9-98b2-dfc63b5835d6?api-version=2021-06-01

以下输出显示了对已成功分配的设备返回的响应。 请注意 status 属性为 assignedregistrationState.assignedHub 属性设置为在其中预配了设备的 IoT 中心。

HTTP/1.1 200 OK
Date: Wed, 31 Aug 2022 22:05:23 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
x-ms-request-id: ffb98d42-023e-4e75-afb0-1807ff091cbb
Strict-Transport-Security: max-age=31536000; includeSubDomains

{
   "operationId":"5.316aac5bdc130deb.b1e02da8-c3a0-4ff2-a121-7ea7a6b7f550",
   "status":"assigned",
   "registrationState":{
      "registrationId":"my-symkey-device",
      "createdDateTimeUtc":"2022-08-31T22:02:50.5163352Z",
      "assignedHub":"MyExampleHub.azure-devices.net",
      "deviceId":"my-symkey-device",
      "status":"assigned",
      "substatus":"initialAssignment",
      "lastUpdatedDateTimeUtc":"2022-08-31T22:02:50.7370676Z",
      "etag":"IjY5MDAzNTUyLTAwMDAtMDMwMC0wMDAwLTYzMGZkYThhMDAwMCI="
   }
}

发送遥测消息

在发送遥测消息之前,需要为设备分配到的 IoT 中心创建 SAS 令牌。 使用为 DPS 实例的 SAS 令牌签名时所用的相同主密钥或派生设备密钥来为此令牌签名。

为 IoT 中心创建 SAS 令牌

若要创建 SAS 令牌,可以运行为 DPS 实例创建令牌时使用的代码,不过需要做出以下更改:

uri = '[resource_uri]'
key = '[device_key]'
expiry = [expiry_in_seconds]
policy= None

其中:

  • [resource_uri] 是你尝试使用此令牌访问的资源的 URI。 对于向 IoT 中心发送消息的设备,其格式为 [iot-hub-host-name]/devices/[device-id]

    • 对于 [iot-hub-host-name],请使用在上一部分所述的 assignedHub 属性中返回的 IoT 中心主机名。

    • 对于 [device-id],请使用在上一部分所述的 deviceId 属性中返回的设备 ID。

  • [device_key] 是与设备关联的设备密钥。 此密钥是在单独注册中为你指定或自动生成的密钥,或者是组注册的派生密钥。 (它与前面用来为 DPS 创建令牌的密钥相同。)

    • 如果使用的是单独注册,请使用在使用单独注册中保存的主密钥。

    • 如果使用的是注册组,请使用在使用注册组中生成的派生设备密钥。

  • [expiry_in_seconds] 是此 SAS 令牌的有效期(以秒为单位)。

  • policy=None:向 IoT 中心发送遥测数据的设备不需要策略,因此该参数设置为 None

向名为 MyExampleHub 的 IoT 中心发送遥测数据的、其令牌有效期为一小时的名为 my-symkey-device 的设备的一组示例输入如下所示:

uri = 'MyExampleHub.azure-devices.net/devices/my-symkey-device'
key = '18RQk/hOPJR9EbsJlk2j8WA6vWaj/yi+oaYg7zmxfQNdOyMSu+SJ8O7TSlZhDJCYmn4rzEiVKIzNiVAWjLxrGA=='
expiry = 3600
policy= None

以下输出显示了这些输入的示例 SAS 令牌:

SharedAccessSignature sr=MyExampleHub.azure-devices.net%2Fdevices%2Fmy-symkey-device&sig=f%2BwW8XOKeJOtiPc9Iwjc4OpExvPM7NlhM9qxN2a1aAM%3D&se=1663119026

若要详细了解如何为 IoT 中心创建 SAS 令牌,包括其他编程语言中的示例代码,请参阅使用共享访问签名控制对 IoT 中心的访问

注意

为方便起见,可以使用 Azure CLI az IoT hub generate-sas-token 命令获取已注册到 IoT 中心的设备的 SAS 令牌。 例如,以下命令生成持续时间为一小时的 SAS 令牌。 对于 {iothub_name},只需获取主机名的第一个组成部分,例如 MyExampleHub

az iot hub generate-sas-token -d {device_id} -n {iothub_name}

将数据发送到 IoT 中心

调用 IoT 中心发送设备事件 REST API 将遥测数据发送到设备。

使用以下 curl 命令:

curl -L -i -X POST -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: [sas_token]' -d '{"temperature": 30}' https://[assigned_iot_hub_name].azure-devices.net/devices/[device_id]/messages/events?api-version=2020-03-13

其中:

  • -X POST 告知 curl 这是一个 HTTP POST 命令。 对于此 API 调用是必需的。

  • -H 'Content-Type: application/json' 告知 IoT 中心我们正在发布 JSON 内容,其值必须是“application/json”。

  • -H 'Content-Encoding: utf-8' 告知 IoT 中心我们对消息正文使用的编码。 请根据你的操作系统/客户端设置适当的值;但是,值通常是 utf-8

  • -H 'Authorization: [sas_token]' 告知 IoT 中心使用你的 SAS 令牌进行身份验证。 请将 [sas_token] 替换为针对分配的 IoT 中心生成的令牌。

  • -d '{"temperature": 30}'–d 参数是发布的消息的“数据”或正文。 对于本文,我们将发布单个温度数据点。 内容类型指定为 application/json,因此对于此请求,正文为 JSON。 请注意,对于 curl,需要将它括在单引号中;否则需要转义 JSON 中的双引号。

  • 最后一个参数是要发布到的 URL。 对于“发送设备事件”API,该 URL 是:https://[assigned_iot_hub_name].azure-devices.net/devices/[device_id]/messages/events?api-version=2020-03-13

    • 请将 [assigned_iot_hub_name] 替换为设备分配到的 IoT 中心的名称。

    • 请将 [device_id] 替换为注册设备时分配的设备 ID。 对于通过注册组预配的设备,设备 ID 将是注册 ID。 对于单独注册,可以选择性地指定一个与注册条目中的注册 ID 不同的设备 ID。

例如,对于设备 ID 为 my-symkey-device 的、将遥测数据点发送到名为 MyExampleHub 的 IoT 中心的设备:

curl -L -i -X POST -H 'Content-Type: application/json' -H 'Content-Encoding:  utf-8' -H 'Authorization: SharedAccessSignature sr=MyExampleHub.azure-devices.net%2Fdevices%2Fmy-symkey-device&sig=f%2BwW8XOKeJOtiPc9Iwjc4OpExvPM7NlhM9qxN2a1aAM%3D&se=1663119026' -d '{"temperature": 30}' https://MyExampleHub.azure-devices.net/devices/my-symkey-device/messages/events?api-version=2020-03-13

成功的调用将返回类似于下面的响应:

HTTP/1.1 204 No Content
Content-Length: 0
Vary: Origin
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: 9e278582-3561-417b-b807-76426195920f
Date: Wed, 14 Sep 2022 00:32:53 GMT

后续步骤