Azure Device Provisioning Service (DPS) で個別登録を試してみた

Azure Device Provisioning Service (DPS) で個別登録を試してみた

Clock Icon2025.05.23

はじめに

コンサルティング部の神野です。

前回までは Azure Device Provisioning Service (DPS) でグループ登録をご紹介してきましたが、今回は個別登録について書いてみました。

https://dev.classmethod.jp/articles/azure-device-provisioning-service-dps/

https://dev.classmethod.jp/articles/azure-device-provisioning-service-dps-x-509/

グループ登録は複数のデバイスをまとめて管理する方法でしたが、個別登録では1台のデバイスごとに詳細な設定を行うことができます。少数のデバイスを扱う場合や、デバイスごとに証明書やキーの管理が必要な場合には、個別登録の方が適しているケースが多いのではないかと思います!

今回は対称キーを使った個別登録と、X.509証明書を使った個別登録の両方を1つの記事で試してみます。

今回やることの概要

下記ステップで進めていきます。グループ登録とは異なり、「個別登録」を作成することで、デバイス1台ずつに細かい設定を行うことができるようになります。

  1. Azure IoT Hubの作成(前回同様のため割愛)
  2. Azure IoT Hub Device Provisioning Service (DPS) の作成(前回同様のため割愛)
  3. IoT HubとDPSのリンク (前回同様のため割愛)
  4. 対称キーを使った個別登録の作成
    • DPSに個別のデバイスエントリを作成し、固有の対称キーで認証する設定をします
  5. X.509証明書を使った個別登録の作成
    • デバイス固有の証明書をアップロードし、証明書で認証する設定をします
  6. Pythonスクリプトでデバイスをシミュレート
    • 両方の認証方式でデバイスをシミュレートします
  7. 動作確認
    • 個別に設定したデバイスがIoT Hubに登録されることを確認します

作業としては手順4から進めていき、手順3までは完了している前提となります。

プロビジョニングのイメージ

プロビジョニング自体のイメージは変わらないので、前回までと同じ図を記載しております。

CleanShot 2025-05-23 at 13.27.09@2x

ただ個別登録とグループ登録って結局何が違うかをわかりやすくするために下記図を記載してみました!

CleanShot 2025-05-23 at 11.53.17@2x

ざっと特徴としては、個別登録ではデバイス1台につき1つの登録を行う形となります。
対称キーの場合はデバイス毎に固有のキーを個別設定し、X.509証明書の場合はデバイス固有の証明書を個別にアップロード・管理します。

個別登録とグループ登録の違いについて

個別登録の設定に入る前に、グループ登録との違いについて整理してみたいと思います。
対称キーの場合とX.509証明書の場合でそれぞれ違いを考えてみます。

対称キーの管理方法の違い

グループ登録の場合

グループに対するマスターキーを使って個々のデバイスのキーを導出するやり方となります。

特徴
  • マスターキー
    • グループ全体で1つのマスターキーを設定
  • デバイスキーの導出
    • 各デバイスは登録IDとマスターキーを使って、デバイス固有のキーをHMAC-SHA256で導出
    • Azure CLIでの導出
      • az iot dps enrollment-group compute-device-keyコマンドでデバイス固有キーを計算

計算の実行コマンドはこのような形となります。

# グループ登録でのデバイスキー導出例
az iot dps enrollment-group compute-device-key \
    --key <YOUR_ENROLLMENT_GROUP_PRIMARY_KEY> \
    --registration-id <YOUR_DEVICE_REGISTRATION_ID>

個別登録の場合

一方で個別登録の場合は、DPSに登録した時点で固有のキーが生成されます。
グループ登録で行った計算は不要となります。個別登録なのでその通りですね。

特徴
  • 個別キー
    • 各デバイス専用の対称キーをDPSが自動生成、または手動で設定
  • キーの導出なし
    • デバイスキーの導出処理は不要。DPSで生成された主キーをそのまま使用
  • 直接認証
    • デバイスは個別登録で設定された主キーを直接使用してDPSに認証

X.509証明書の管理方法の違い

グループ登録の場合

事前にDPSにCA証明書を登録して、登録したCAによって発行されたデバイス証明書を自動的にプロビジョニングするやり方となります。AWS IoT CoreでいうJITRやJITPがイメージとしては近いですね。

特徴
  • ルート証明書
    • CA証明書を事前にDPSに登録・検証
  • 証明書チェーン
    • デバイス証明書はルート証明書で署名された証明書チェーンを構成
  • デバイス証明書
    • 各デバイスは個別に証明書を作成するが、DPSには登録しない

個別登録の場合

一方で個別の登録の場合は、作成したデバイス証明書を直接DPSにアップロードするやり方になります。

特徴
  • デバイス証明書直接登録
    • 各デバイスの証明書を直接DPSにアップロード
  • ルート証明書不要
    • ルート証明書やCA証明書の設定は不要
  • 個別管理
    • 各デバイス証明書を個別にDPSで管理・検証

設定・管理の違い

項目 グループ登録 個別登録
設定単位 グループ単位(1つの設定で複数デバイス対応) デバイス単位(1台ずつ個別設定)
スケーラビリティ 高い(大量デバイスに適している) 低い(少数デバイス向け)
設定の柔軟性 基本的にグループ共通設定 デバイス毎に詳細設定可能
運用負荷 低い(一括管理) 高い(個別管理)
セキュリティ分離 1つのマスターキーを共有 デバイス毎に独立したキー
トラブルシューティング グループ全体への影響 個別デバイスのみへの影響
対称キーの扱い マスターキーから導出 個別に生成・管理
証明書の扱い ルート証明書で一括管理 デバイス証明書を個別アップロード

所感ですが、大量のデバイスに対して一度に証明書やキーを発行して運用コストを下げたい場合はグループ登録、個々に検証・登録などを行なってよりセキュリティ的に厳密に証明書やキーをデバイス単位で管理したいなどの要件があれば個別登録になるのかなと思いました。

ただ個別登録の場合は、デバイス毎に証明書をアップロードする、もしくはキーを共有する必要があるので製造時のステップが少し複雑、運用コストが上がる点も考慮する必要があるかと思いました。

準備するもの

  • Azureアカウント
  • Python実行環境: Python 3.10以上
  • Azure CLI
  • OpenSSL: X.509証明書を使う場合に証明書の作成に使用
  • テキストエディタ: Pythonスクリプトを編集するために使います

対称キーを使った個別登録の作成

まずは対称キーを使った個別登録から始めてみます。

DPSに個別登録を作成する

  1. DPSのポータルページを開きます。

  2. 左側のメニューから「設定」セクションの「登録を管理します」を選択します。

  3. 「個々の登録」タブを押下して、上部にある「+個別の登録の追加」ボタンをクリックします。

CleanShot 2025-05-23 at 08.23.21@2x

個別登録の設定(対称キー)

登録の詳細

  • メカニズム
    • 対称キー を選択
  • 対称キーの設定
    • 対称キーを自動的に生成します にチェックを入れる
  • 登録ID
    • デバイス固有の識別子を入力します(例: individual-device1

CleanShot 2025-05-23 at 08.24.20@2x

IoT ハブの割り当て

  • この登録を割り当てることができるIoT ハブを選択してください
    • 事前に作成したIoT ハブを選択
  • 割り当てポリシー
    • デフォルトの割り当てポリシー

CleanShot 2025-05-23 at 08.03.48@2x 1

確認と作成

設定内容を確認して問題なければ「保存」ボタンを押下します。

作成が完了したら、作成した個別登録を選択し、主キーをコピーしてメモしておきます。

CleanShot 2025-05-23 at 08.25.48@2x 1

X.509証明書を使った個別登録の作成

次に、X.509証明書を使った個別登録を作成してみます。

デバイス証明書の準備

個別登録では、デバイス毎に個別の証明書を作成します。

# 作業ディレクトリの作成
mkdir azure-iot-individual-certs
cd azure-iot-individual-certs

# デバイス1用の証明書作成
openssl genrsa -out individual-device1-x509.key 2048

# CSRの生成
openssl req -new -key individual-device1-x509.key -out individual-device1-x509.csr -subj \"/CN=individual-device1-x509\"

# 自己署名証明書の生成(SHA256を明示的に指定)
openssl x509 -req -in individual-device1-x509.csr -signkey individual-device1-x509.key -out individual-device1-x509.pem -days 365 -sha256

DPSに個別登録を作成する(X.509証明書)

1つ目のデバイスの個別登録

  1. 「+個別の登録の追加」をクリックします。

  2. 以下の設定を行います。

    • メカニズム: X.509 を選択
    • 主証明書 .pem または .cer ファイル: 作成した individual-device1-x509.pem をアップロード
    • 登録ID: individual-device1-x509(証明書のCNと同じになります)
    • この登録を有効にする: チェック
    • IoT ハブの割り当て: 事前に作成したIoT ハブを選択
      CleanShot 2025-05-23 at 08.30.22@2x
      CleanShot 2025-05-23 at 08.27.23@2x
  3. 問題なければ「確認と保存」ボタンを押下します。

個別登録の時は直接、デバイス証明書を1つずつアップロードするので大量に繰り返す場合は大変になりそうですね・・・

Pythonスクリプトでデバイスをシミュレートする

個別登録の設定が完了したので、デバイスをシミュレートしてみます。

必要なPythonライブラリのインストール

pip install azure-iot-device

対称キー用のPythonスクリプト

dps_individual_symmetric_test.py というファイルを作成します。

dps_individual_symmetric_test.py
import os
import asyncio
import datetime
import random
import json
from azure.iot.device.aio import ProvisioningDeviceClient
from azure.iot.device.aio import IoTHubDeviceClient

# DPSの設定情報 - ご自身の環境に合わせて変更してください
PROVISIONING_HOST = "global.azure-devices-provisioning.net"
ID_SCOPE = "xxx"  # DPSインスタンスのIDスコープ

# 個別登録のデバイス情報(1台分)
REGISTRATION_ID = "individual-device1"
SYMMETRIC_KEY = "xxx"  # individual-device1の主キー

async def provision_device():
    print(f"[{REGISTRATION_ID}] 対称キーを使ってDPSに接続し、IoT Hubへの登録を開始します...")

    # DPSクライアントの作成
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
        provisioning_host=PROVISIONING_HOST,
        registration_id=REGISTRATION_ID,
        id_scope=ID_SCOPE,
        symmetric_key=SYMMETRIC_KEY,
    )

    # DPSに接続して登録
    print(f"[{REGISTRATION_ID}] DPSに接続して登録を行っています...")
    registration_result = await provisioning_device_client.register()

    print(f"[{REGISTRATION_ID}] DPS登録ステータス: {registration_result.status}")
    if registration_result.status == "assigned":
        print(f"[{REGISTRATION_ID}] デバイスがIoT Hubに登録されました: {registration_result.registration_state.assigned_hub}")
        print(f"[{REGISTRATION_ID}] デバイスID: {registration_result.registration_state.device_id}")

        # IoT Hubに接続してテストメッセージを送信
        device_client = IoTHubDeviceClient.create_from_symmetric_key(
            symmetric_key=SYMMETRIC_KEY,
            hostname=registration_result.registration_state.assigned_hub,
            device_id=registration_result.registration_state.device_id,
        )

        await device_client.connect()
        print(f"[{REGISTRATION_ID}] IoT Hub に接続しました!")

        # テストメッセージを送信
        for i in range(3):
            message = {
                "temperature": round(random.uniform(20.0, 30.0), 1),
                "humidity": round(random.uniform(50.0, 70.0), 1),
                "message": f"Hello from individual enrollment device {REGISTRATION_ID} (message {i+1}/3)",
                "timestamp": datetime.datetime.now().isoformat(),
                "deviceId": REGISTRATION_ID
            }
            print(f"[{REGISTRATION_ID}] メッセージを送信: {json.dumps(message)}")
            await device_client.send_message(json.dumps(message))
            await asyncio.sleep(1)

        await device_client.disconnect()
        print(f"[{REGISTRATION_ID}] IoT Hubから切断しました")
    else:
        print(f"[{REGISTRATION_ID}] デバイスの登録に失敗しました。ステータス: {registration_result.status}")

async def main():
    print("対称キー個別登録のテストを開始します...")
    await provision_device()
    print("処理が完了しました")

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

X.509証明書用のPythonスクリプト

dps_individual_x509_test.py というファイルを作成します。

dps_individual_x509_test.py
import os
import asyncio
import datetime
import random
import json
from azure.iot.device import X509
from azure.iot.device.aio import ProvisioningDeviceClient
from azure.iot.device.aio import IoTHubDeviceClient

# DPSの設定情報 - ご自身の環境に合わせて変更してください
PROVISIONING_HOST = "global.azure-devices-provisioning.net"
ID_SCOPE = "xxx"  # DPSインスタンスのIDスコープ

# 個別登録のデバイス証明書情報(1台分)
REGISTRATION_ID = "individual-device1-x509"
CERT_PATH = "./individual-device1-x509.pem"
KEY_PATH = "./individual-device1-x509.key"

async def provision_device():
    print(f"[{REGISTRATION_ID}] X.509証明書を使ってDPSに接続し、IoT Hubへの登録を開始します...")

    # X.509証明書の設定
    x509 = X509(
        cert_file=CERT_PATH,
        key_path=KEY_PATH,
        pass_phrase=None
    )

    # DPSクライアントの作成
    provisioning_device_client = ProvisioningDeviceClient.create_from_x509_certificate(
        provisioning_host=PROVISIONING_HOST,
        registration_id=REGISTRATION_ID,
        id_scope=ID_SCOPE,
        x509=x509,
    )

    # DPSに接続して登録
    print(f"[{REGISTRATION_ID}] DPSに接続して登録を行っています...")
    registration_result = await provisioning_device_client.register()

    print(f"[{REGISTRATION_ID}] DPS登録ステータス: {registration_result.status}")
    if registration_result.status == "assigned":
        print(f"[{REGISTRATION_ID}] デバイスがIoT Hubに登録されました: {registration_result.registration_state.assigned_hub}")
        print(f"[{REGISTRATION_ID}] デバイスID: {registration_result.registration_state.device_id}")

        # IoT Hubに接続してテストメッセージを送信
        device_client = IoTHubDeviceClient.create_from_x509_certificate(
            x509=x509,
            hostname=registration_result.registration_state.assigned_hub,
            device_id=registration_result.registration_state.device_id,
        )

        await device_client.connect()
        print(f"[{REGISTRATION_ID}] IoT Hub に接続しました!")

        # テストメッセージを送信
        for i in range(3):
            message = {
                "temperature": round(random.uniform(20.0, 30.0), 1),
                "humidity": round(random.uniform(50.0, 70.0), 1),
                "message": f"Hello from individual X.509 device {REGISTRATION_ID} (message {i+1}/3)",
                "timestamp": datetime.datetime.now().isoformat(),
                "deviceId": REGISTRATION_ID
            }
            print(f"[{REGISTRATION_ID}] メッセージを送信: {json.dumps(message)}")
            await device_client.send_message(json.dumps(message))
            await asyncio.sleep(1)

        await device_client.disconnect()
        print(f"[{REGISTRATION_ID}] IoT Hubから切断しました")
    else:
        print(f"[{REGISTRATION_ID}] デバイスの登録に失敗しました。ステータス: {registration_result.status}")

async def main():
    print("X.509証明書個別登録のテストを開始します...")
    await provision_device()
    print("処理が完了しました")

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

スクリプトの修正と実行

各スクリプトを実行する前に、以下の値を環境に合わせて修正してください:

  • ID_SCOPE: DPSの概要ページから取得
    CleanShot 2025-05-23 at 08.32.46@2x
  • 対称キーの場合:各デバイスの symmetric_key を個別登録で生成された主キーに設定
  • X.509証明書の場合:証明書ファイルのパスを実際のパスに修正
# 対称キーのテスト
python dps_individual_symmetric_test.py

# X.509証明書のテスト
python dps_individual_x509_test.py
dps_individual_symmetric_test.pyの実行ログ
[individual-device1] 対称キーを使ってDPSに接続し、IoT Hubへの登録を開始します...
[individual-device1] DPSに接続して登録を行っています...
[individual-device1] DPS登録ステータス: assigned
[individual-device1] デバイスがIoT Hubに登録されました: sample-iot-hub.azure-devices.net
[individual-device1] デバイスID: individual-device1
[individual-device1] IoT Hub に接続しました!
[individual-device1] メッセージを送信: {"temperature": 27.4, "humidity": 62.0, "message": "Hello from individual enrollment device individual-device1 (message 1/3)", "timestamp": "2025-05-23T08:35:41.377291", "deviceId": "individual-device1"}
[individual-device1] メッセージを送信: {"temperature": 28.6, "humidity": 66.8, "message": "Hello from individual enrollment device individual-device1 (message 2/3)", "timestamp": "2025-05-23T08:35:42.667731", "deviceId": "individual-device1"}
[individual-device1] メッセージを送信: {"temperature": 23.3, "humidity": 69.0, "message": "Hello from individual enrollment device individual-device1 (message 3/3)", "timestamp": "2025-05-23T08:35:43.893072", "deviceId": "individual-device1"}
[individual-device1] IoT Hubから切断しました
処理が完了しました
dps_individual_x509_test.pyの実行ログ
[individual-device1-x509] X.509証明書を使ってDPSに接続し、IoT Hubへの登録を開始します...
[individual-device1-x509] DPSに接続して登録を行っています...
[individual-device1-x509] DPS登録ステータス: assigned
[individual-device1-x509] デバイスがIoT Hubに登録されました: sample-iot-hub.azure-devices.net
[individual-device1-x509] デバイスID: individual-device1-x509
[individual-device1-x509] IoT Hub に接続しました!
[individual-device1-x509] メッセージを送信: {"temperature": 24.1, "humidity": 56.6, "message": "Hello from individual X.509 device individual-device1-x509 (message 1/3)", "timestamp": "2025-05-23T09:08:07.317973", "deviceId": "individual-device1-x509"}
[individual-device1-x509] メッセージを送信: {"temperature": 21.1, "humidity": 65.8, "message": "Hello from individual X.509 device individual-device1-x509 (message 2/3)", "timestamp": "2025-05-23T09:08:08.573137", "deviceId": "individual-device1-x509"}
[individual-device1-x509] メッセージを送信: {"temperature": 29.0, "humidity": 64.3, "message": "Hello from individual X.509 device individual-device1-x509 (message 3/3)", "timestamp": "2025-05-23T09:08:09.702152", "deviceId": "individual-device1-x509"}
[individual-device1-x509] IoT Hubから切断しました
処理が完了しました

スクリプトは問題なく実行できました!ポータル上でも登録されているか確認してみます。

CleanShot 2025-05-23 at 12.00.23@2x

どちらも問題なく動作して、自動登録されましたね!!

動作確認は今まで同じくポータル上でも簡易的に確認できます。今回は主題はデバイスの自動登録までなので、割愛とさせてください。

az iot hub monitor-events --hub-name YourIoTHubName

おわりに

今回は、Azure Device Provisioning Service (DPS) の個別登録機能を対称キーとX.509証明書の両方で試してみました。

グループ登録と比較すると、個別登録はデバイス毎の細かい制御ができる反面、管理コストが高くなる傾向があると感じました。少数のデバイスを扱う場合や、デバイス毎に異なる設定が必要な場合には、個別登録の柔軟性が非常に有効だと思います。

プロジェクトの要件(デバイス数、運用体制、セキュリティ要件など)を総合的に考慮して、グループ登録と個別登録を適切に使い分けていただければと思います。

今後は、DPSのその他の機能(カスタム割り当てポリシーや再プロビジョニング設定など)についても紹介していけたらと思います!

この記事が少しでも参考になりましたら幸いです!最後までご覧いただきありがとうございましたー!!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.