概要
ファームウェア署名(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-2048 | 2048 bit | 256 byte | 遅い(MCU では数秒も) | 互換性重視の場合 |
| RSA-4096 | 4096 bit | 512 byte | 非常に遅い | 高セキュリティ |
| ECDSA P-256 | 256 bit | 64 byte | 速い | 現在の主流 |
| Ed25519 | 256 bit | 64 byte | 最速 | 新規実装推奨 |
| ECDSA P-384 | 384 bit | 96 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 | 鍵保管・整合性測定 | 署名鍵の安全な保管先 |
ファームウェア署名は「データが改ざんされていないこと」を証明するが、ファームウェアの内容を秘匿するわけではない。秘密性が必要な場合は暗号化と組み合わせる。