みちびきの信号認証(QZNMA)を実機で検証してみた — 内閣府の公開鍵で本物の衛星署名を確かめる

前回(そもそも「信号認証」って何?)で、みちびきの QZNMA(衛星の電波に電子署名を付けて、なりすましを暗号学的に検知する仕組み)を紹介しました。今回はその続き ── 実際に受信機で QZNMA を受信して、内閣府が配布する公開鍵で署名を検証するところまでやります。結論から言うと、本物のみちびきの署名を実機で検証できました。ただ、そこに辿り着くまでが沼でした。

検証に必要な2つのピース

QZNMA の署名検証は、印鑑の照合に似ています。要るのは2つ:

  1. 署名(ハンコ) ── みちびきが配信するデジタル署名(DS)
  2. 原本(ハンコが押された書類そのまま) ── 署名の対象になった航法電文の"生ビット"

署名は「原本の中身」に対して計算されているので、検証するには 原本を1ビットも違わず再現して突き合わせる必要があります。この「原本=生の航法電文ビット」を手に入れるのが、今回の最大の関門でした。

沼日記(誤診 → 本質)

沼1: cat がバイナリを破壊する

受信機を USB で繋いで cat /dev/ttyACM0 で覗いたら、データがどこか壊れている。同期語 0x1ACFFC1D0x0ACFFC1D に化ける。

真因: TTY のライン制御(cooked モード)がバイナリの制御文字を勝手に変換していた。stty -F /dev/ttyACM0 raw -echo で raw モードにしたら一発で直った。GNSS のバイナリを生で読むときの基本でした。

沼2: L6E(QZNMA)が出てこない

みちびきの L6 受信機(u-blox NEO-D9C)を繋いだが、出てくるのは L6D(CLAS 補正)ばかりで L6E(QZNMA が乗る帯)がゼロ

真因: 既定設定が CLAS(L6D)専用だった。UBX の設定で L6E を有効化する:

CFG-QZSS-L6_MSGB = 1 (L6E)   # 0x20370060、チャンネルBをL6Eに

UBX-CFG-VALSET を RAM レイヤに送って ACK-ACK を確認 → L6E フレームが流れ出した。MTID の上位3ビット(Vendor ID)が 011b のものが QZNMA、010b は MADOCA という棲み分け。

沼3: 「原本」が手に入らない

ここが本番の沼。署名(DS)は L6E から取れた。でも原本(生の航法電文ビット)が要る。手持ちの高精度受信機は、航法電文を "復号した後の数値"(軌道パラメータ)しかくれない ── 要約版では署名と照合できない。

解決: u-blox の UBX-RXM-SFRBX受信した航法サブフレームの生ビットそのままを出すメッセージ。ZED-F9R で有効化したら、GPS・QZSS・Galileo の生サブフレームがそのまま降ってきた。これが探していた「原本」。

沼4: SFRBX のワード形式を読み解く

SFRBX は1ワード30ビットを32ビット値に詰めて渡してくる。最初サブフレームIDが「5,6,7」など有り得ない値に。実データで形式を特定:

  • 30ビットのワードは下位30ビットに格納(dwrd[29..0]
  • TLM プリアンブル 0x8B(word0 >> 22) & 0xFF
  • サブフレームID は (word1 >> 8) & 7、TOWカウントは (word1 >> 13) & 0x1FFFF

TOW が +1 されるごとにサブフレームが 1→2→3→4→5 と巡回することを確認して確証が取れた。

沼5: QZSS は自己完結だった

調べると、QZSS 自身の LNAV を認証する署名は、QZSS の L1C/A 電文の中(サブフレーム5、SV ID=60 のページ)に入っている。つまり:

  • サブフレーム1・2・3 → 原本(RNAV)
  • サブフレーム5(SV60)→ 署名(DSS)

両方とも ZED-F9R の SFRBX から取れる。L6E も専用受信機も要らず、1台で完結

沼6: DSS は3つに割れている

署名(RDS)はサブフレーム5の3ページに 3セグメント(各182ビット)に分割して送られる。3つ集めて再結合すると 540ビットの RDS = KeyID(8) ‖ DS(512) ‖ SALT(16) ‖ 予約(4)。3セグメントは60秒間隔なので、揃えるのに約2分かかる。

沼7: 署名は「特定の瞬間」に紐づく(最大の落とし穴)

組み上げて検証 → 不一致。データ抽出は全部正しいのに通らない。

真因: マスク(署名対象ビットの選択)を調べると、TOW(時刻)も署名対象に含まれていた。つまり署名は「いつでもいい sf1-3」ではなく、ある特定の瞬間(180+N×240秒)に放送された sf1-3 のスナップショットに紐づく(リプレイ防止の良い設計)。

最初の280秒キャプチャは、その RNAV エポックの約30秒手前で終わっていた(あと少し!)。署名対象の原本を撮り逃していただけだった。キャプチャを伸ばして RNAV エポックまで含めたら ──

検証成功

*** LIVE QZNMA VERIFICATION SUCCESS ***
  ✅ VERIFIED  QZS sv2  KeyID=4  ECDSA P-256: VALID
  ✅ VERIFIED  QZS sv3  KeyID=4  ECDSA P-256: VALID
  ✅ VERIFIED  QZS sv7  KeyID=4  ECDSA P-256: VALID

可視だった QZSS 3機すべて、内閣府配布の公開鍵(KeyID 4)で署名検証成功。本物のみちびきの信号認証を、自前のホスト側コードで確かめられました。

検証の流れ(技術まとめ)

[ZED-F9R] UBX-RXM-SFRBX で QZSS L1C/A 生サブフレームを取得
   ├─ sf1,2,3            → RNAV(原本、900ビット)
   └─ sf5 (SV ID=60) ×3  → DSS 3セグメント再結合 → RDS(KeyID/DS/SALT)
        ↓
[RAND 再構成]  RAND = KeyID(8) ‖ MNAV ‖ SALT(16) ‖ パディング
        ↑      MNAV = RNAV AND MASK(時刻・暦の必要ビットだけ選択)
        ↓
[SHA-256] → [ECDSA P-256 検証]  内閣府公開鍵(KeyID)で DS を検証
        ├─ VALID  → この衛星電文は「本物」✅
        └─ NG     → なりすましの疑い ⚠️
  • 暗号は ECDSA(NIST P-256)+ SHA-256。鍵は内閣府配布の DER 形式(KeyID で選択)。
  • 仕様は公開されている IS-QZSS-SAS-001(みちびき信号認証サービスのインターフェース仕様書)に準拠。
  • 全部 オフラインで完結(ネット接続不要、手元の公開鍵だけで判定)。

まとめ

  • QZNMA 検証には「署名(DSS)」と「原本(生の航法電文 RNAV)」の両方が要る
  • 原本は UBX-RXM-SFRBX(生サブフレーム)で取る ── 復号済みエフェメリスでは不可
  • QZSS 自己認証は sf1-3=原本 / sf5(SV60)=署名 で、受信機1台で完結
  • 署名は 特定時刻のスナップショットに紐づく(TOW も署名対象)=リプレイ耐性
  • 結果、本物のみちびきの署名を内閣府の鍵で実機検証できた

衛星から降ってくる電波が「本物か」を、推測ではなく暗号で白黒つけられる時代。実際に手元の受信機とコードで確かめられたのは、なかなか感慨深い体験でした。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

IP: 取得中...
216.73.216.103216.73.216.103