セキュリティ

ファームウェア署名

改ざん検知のための電子署名技術。

概要

ファームウェア署名(Firmware Signing)とは、組み込みデバイスにインストールするファームウェアイメージに電子署名を付与し、デバイス側でその署名を検証することで、ファームウェアの正真性(authenticity)と完全性(integrity)を保証する技術である。

開発者または製造者が秘密鍵でファームウェアに署名し、デバイスは対応する公開鍵で署名を検証する。署名が正しければ「正規の開発者が作成した、改ざんされていないファームウェア」であると判断でき、不正なファームウェアの書き込みを防止できる。

セキュアブートの実現に欠かせない要素技術であり、OTA(Over-the-Air)アップデートのセキュリティ確保にも中心的な役割を果たす。


歴史・背景

電子署名の理論は 1976 年に Diffie と Hellman が公開鍵暗号の概念を発表し、1977 年に RSA が実装例として登場したことで基礎が確立された。

組み込み向けのファームウェア署名の普及は比較的新しく、次の流れで広まった。

  • 2000 年代前半: スマートカードや SIM カードでは早期から署名ベースの更新が行われていた。
  • 2010 年代: スマートフォン(Android/iOS)の OTA 更新で一般化。Android では OTA パッケージへの RSA 署名が必須となった。
  • MCUboot (2017年): Nordic Semiconductor 主導でオープンソースの MCU 向けセキュアブートローダーが登場し、中小規模の IoT デバイスでもファームウェア署名が現実的になった。
  • IoT セキュリティ標準: ETSI EN 303 645、NIST SP 800-213 など IoT 機器のセキュリティ標準でファームウェア署名が要件として明記されるようになった。

技術仕様

署名アルゴリズムの比較

アルゴリズム鍵長署名サイズ速度推奨度
RSA-20482048 bit256 byte遅い(MCU では数秒も)互換性重視の場合
RSA-40964096 bit512 byte非常に遅い高セキュリティ
ECDSA P-256256 bit64 byte速い現在の主流
Ed25519256 bit64 byte最速新規実装推奨
ECDSA P-384384 bit96 byte普通高セキュリティ

MCU での署名検証コスト

RSA-2048 検証: ARM Cortex-M4 @ 120MHz で ≈ 200〜500ms
ECDSA P-256:  ARM Cortex-M4 @ 120MHz で ≈ 300〜600ms
Ed25519:      ARM Cortex-M4 @ 120MHz で ≈ 30〜100ms
(実装・ライブラリにより大幅に異なる)

ハードウェア暗号アクセラレータ(STM32 の PKA など)を使うと 10 倍以上高速化できる。

イメージフォーマット

MCUboot 形式のファームウェアイメージ構造:

┌──────────────────────────────────────────┐
│  Image Header (32 bytes)                 │
│  magic, load_addr, hdr_size, img_size,   │
│  flags, version                          │
├──────────────────────────────────────────┤
│  Firmware Binary (可変長)                │
│  実際のファームウェアコード               │
├──────────────────────────────────────────┤
│  TLV Area (Type-Length-Value)            │
│  ├── SHA-256 ハッシュ (32 bytes)         │
│  ├── 署名 (ECDSA/RSA, 64〜256 bytes)    │
│  └── 証明書(オプション)                │
└──────────────────────────────────────────┘

動作原理

署名の生成(開発・ビルド時)

# imgtool を使ったファームウェア署名(MCUboot 対応)
# 1. 鍵ペア生成
# imgtool keygen -k signing_key.pem -t ecdsa-p256

# 2. ファームウェアへの署名付与
# imgtool sign \
#     --key signing_key.pem \
#     --version 1.2.3+4 \
#     --header-size 0x200 \
#     --slot-size 0x60000 \
#     firmware.bin \
#     firmware_signed.bin

import subprocess
import hashlib

def sign_firmware(firmware_path: str, key_path: str, version: str) -> str:
    """MCUboot imgtool でファームウェアに署名"""
    output_path = firmware_path.replace('.bin', '_signed.bin')
    cmd = [
        'imgtool', 'sign',
        '--key', key_path,
        '--version', version,
        '--header-size', '0x200',
        '--slot-size', '0x60000',
        firmware_path,
        output_path
    ]
    subprocess.run(cmd, check=True)
    return output_path

検証処理(デバイス側)

/* MCUboot での署名検証フロー(概念コード) */
#include "mcuboot/bootutil.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/sha256.h"

/* デバイスに焼き付けられた公開鍵 */
static const uint8_t FIRMWARE_PUBLIC_KEY[] = {
    /* ビルド時に公開鍵を埋め込む */
    0x04, 0x1a, 0x2b, /* ... ECDSA P-256 公開鍵 65 bytes ... */
};

int verify_firmware_image(const struct image_header *hdr,
                           const uint8_t *fw_data, size_t fw_size) {
    uint8_t hash[32];
    mbedtls_sha256_context sha_ctx;

    /* 1. ファームウェアのハッシュを計算 */
    mbedtls_sha256_init(&sha_ctx);
    mbedtls_sha256_starts(&sha_ctx, 0); /* SHA-256 */
    mbedtls_sha256_update(&sha_ctx, (uint8_t *)hdr, sizeof(*hdr));
    mbedtls_sha256_update(&sha_ctx, fw_data, fw_size);
    mbedtls_sha256_finish(&sha_ctx, hash);

    /* 2. TLV エリアから署名を取得 */
    const uint8_t *signature = get_tlv_signature(hdr);
    size_t sig_len = get_tlv_signature_length(hdr);

    /* 3. ECDSA 署名検証 */
    mbedtls_ecdsa_context ecdsa_ctx;
    mbedtls_ecdsa_init(&ecdsa_ctx);
    int ret = mbedtls_ecp_group_load(&ecdsa_ctx.grp, MBEDTLS_ECP_DP_SECP256R1);
    if (ret != 0) return -1;

    /* 公開鍵をロード */
    mbedtls_ecp_point_read_binary(&ecdsa_ctx.grp, &ecdsa_ctx.Q,
                                   FIRMWARE_PUBLIC_KEY,
                                   sizeof(FIRMWARE_PUBLIC_KEY));

    /* 検証 */
    ret = mbedtls_ecdsa_read_signature(&ecdsa_ctx, hash, 32, signature, sig_len);
    mbedtls_ecdsa_free(&ecdsa_ctx);

    return ret; /* 0: 成功, 非0: 失敗 */
}

CI/CD パイプラインとの統合

# GitHub Actions でのファームウェア署名ワークフロー例
name: Firmware Build and Sign

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: ビルド
        run: make firmware.bin

      - name: 署名(秘密鍵は GitHub Secrets に保管)
        env:
          SIGNING_KEY: ${{ secrets.FIRMWARE_SIGNING_KEY }}
        run: |
          echo "$SIGNING_KEY" > /tmp/signing_key.pem
          imgtool sign \
            --key /tmp/signing_key.pem \
            --version $(git describe --tags) \
            --header-size 0x200 \
            --slot-size 0x60000 \
            firmware.bin \
            firmware_signed.bin
          rm /tmp/signing_key.pem

      - name: 署名済みファームウェアをリリース
        uses: softprops/action-gh-release@v1
        with:
          files: firmware_signed.bin

用途・ユースケース

OTA アップデートのセキュリティ

IoT デバイスへの無線ファームウェア更新時、改ざんされたファームウェアや悪意ある第三者が配布する偽ファームウェアのインストールを防止する。AWS IoT OTA Job、Azure IoT Hub、Nordic DFU など主要な OTA フレームワークは署名検証を標準サポートしている。

製品の真正性保証

デバイスメーカーが「自社が製造したデバイスに自社が承認したファームウェアのみを動作させる」ことを保証する手段。サードパーティによる無認可ファームウェアの実行を防ぎ、安全性・品質の担保に使用される。

規制対応

医療機器(FDA サイバーセキュリティガイダンス)、車載機器(ISO 21434、UN R155)、産業機器(IEC 62443)など各種規制でファームウェア署名が要件として明記されている。


実装・開発のポイント

鍵管理戦略

秘密鍵管理の推奨構成:

製造者ルート CA(オフライン、HSM 保管)
    └── コード署名中間 CA(オンライン、HSM)
            └── ビルド署名鍵(CI/CD、有効期限あり)

- ルート鍵は絶対にオンライン環境に置かない
- CI/CD での署名鍵は定期ローテーションする
- 鍵の有効期限切れに注意(フィールドデバイスの長期運用)

ロールバック防止との組み合わせ

署名検証だけでは、正規だが古い(脆弱な)ファームウェアへのダウングレードを防げない。バージョン番号を OTP に記録し、古いバージョンの実行を拒否するアンチロールバック機能と組み合わせる必要がある。

デバッグビルドと本番ビルドの分離

開発中はデバッグ用の鍵(テストキー)で署名し、量産時に本番鍵に切り替える運用とする。MCUboot は CONFIG_MCUBOOT_SIGNATURE_KEY_FILE で切り替えられる。


他技術との比較

技術役割ファームウェア署名との関係
セキュアブート起動時検証の仕組みファームウェア署名の検証タイミング
公開鍵暗号署名の暗号基盤署名生成・検証に使用
OTA セキュリティ更新の安全性全般ファームウェア署名はその核心要素
暗号化データの秘匿署名は「正真性」、暗号化は「秘密性」
TPM鍵保管・整合性測定署名鍵の安全な保管先

ファームウェア署名は「データが改ざんされていないこと」を証明するが、ファームウェアの内容を秘匿するわけではない。秘密性が必要な場合は暗号化と組み合わせる。

関連用語

参考リンク