前回(そもそも「信号認証」って何?)で、みちびきの QZNMA(衛星の電波に電子署名を付けて、なりすましを暗号学的に検知する仕組み)を紹介しました。今回はその続き ── 実際に受信機で QZNMA を受信して、内閣府が配布する公開鍵で署名を検証するところまでやります。結論から言うと、本物のみちびきの署名を実機で検証できました。ただ、そこに辿り着くまでが沼でした。
検証に必要な2つのピース
QZNMA の署名検証は、印鑑の照合に似ています。要るのは2つ:
- 署名(ハンコ) ── みちびきが配信するデジタル署名(DS)
- 原本(ハンコが押された書類そのまま) ── 署名の対象になった航法電文の"生ビット"
署名は「原本の中身」に対して計算されているので、検証するには 原本を1ビットも違わず再現して突き合わせる必要があります。この「原本=生の航法電文ビット」を手に入れるのが、今回の最大の関門でした。
沼日記(誤診 → 本質)
沼1: cat がバイナリを破壊する
受信機を USB で繋いで cat /dev/ttyACM0 で覗いたら、データがどこか壊れている。同期語 0x1ACFFC1D が 0x0ACFFC1D に化ける。
真因: 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 も署名対象)=リプレイ耐性
- 結果、本物のみちびきの署名を内閣府の鍵で実機検証できた
衛星から降ってくる電波が「本物か」を、推測ではなく暗号で白黒つけられる時代。実際に手元の受信機とコードで確かめられたのは、なかなか感慨深い体験でした。

コメントを残す