你当前正在访问 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 提示符下输入所有命令。

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

Prerequisites

  • 如果没有 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 及其结构配合使用,请参阅 使用共享访问签名和安全令牌控制对 Azure IoT 中心设备预配服务(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.nethttps://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

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

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

后续步骤