2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

耐量子暗号(PQC)入門:RFC 9964でJWTの署名はどう変わる?OpenSSLでML-DSAを試してみた

2
Last updated at Posted at 2026-10-27

1. はじめに

前回の「最新RFCまとめ記事(2026年版)」の最後で、次は耐量子暗号(PQC)かAI×ネットワーク運用を深掘りしたいと書きました。今回はその続編として、PQCを取り上げます。

題材は、前回紹介したRFC 9964(ML-DSAをJOSE/COSEで使うための仕様)です。調べるだけでなく、手元のOpenSSLで実際にML-DSAの鍵を作って署名し、従来のECDSAと比べてみました。

この記事でわかること:

  • なぜ今、暗号の置き換えが急がれているのか
  • ML-DSAとはどんな署名方式か
  • RFC 9964で、JWTなどの署名がどう変わるのか
  • 実際に動かしたときのサイズと速度の差
  • 実務(特にIoT・エネルギー機器)で気をつけたいこと

2. なぜ今PQCなのか

今使われているRSAや楕円曲線暗号(ECDSA、ECDHなど)は、十分な性能の量子コンピュータが実現すると、ショアのアルゴリズムによって破られることがわかっています。まだそのような量子コンピュータは存在しませんが、準備には時間がかかるため、標準化と移行が先に進んでいます。

2-1. 「今盗んで、後で解読する」攻撃

暗号化された通信は、今は読めなくても保存しておけます。将来量子コンピュータが使えるようになった時点で解読する、という攻撃が想定されています(Harvest Now, Decrypt Later)。

これは主に暗号化(鍵交換)の話です。一方、署名は「その時点で偽造できるか」が問題なので、すぐには危険になりません。ただし、ファームウェア署名や長期保存する証書など、何年も検証され続ける署名は早めの対応が必要です。

2-2. NISTによる標準化

米国NISTは2024年8月に、最初のPQC標準3つを公開しました。

規格 名前 用途 元になった方式
FIPS 203 ML-KEM 鍵カプセル化(鍵交換) CRYSTALS-Kyber
FIPS 204 ML-DSA デジタル署名 CRYSTALS-Dilithium
FIPS 205 SLH-DSA デジタル署名(ハッシュベース) SPHINCS+

TLSの鍵交換ではML-KEMを従来方式と組み合わせたハイブリッド方式の導入がすでに進んでいます。今回取り上げるのは、署名側の主力であるML-DSAです。

3. ML-DSAとは

ML-DSA(Module-Lattice-Based Digital Signature Algorithm)は、格子暗号をベースにしたデジタル署名方式です。量子コンピュータでも効率よく解く方法が知られていない「格子上の数学的な問題」の難しさを安全性の根拠にしています。

セキュリティ強度に応じて3つのパラメータセットがあります。サイズはFIPS 204およびRFC 9964のTable 1の値です(単位はバイト)。

パラメータ NISTセキュリティカテゴリ 秘密鍵 公開鍵 署名
ML-DSA-44 2 2,560 1,312 2,420
ML-DSA-65 3 4,032 1,952 3,309
ML-DSA-87 5 4,896 2,592 4,627

比較として、ECDSA(P-256)は公開鍵が65バイト(非圧縮)、署名が64バイト程度です。ML-DSAは署名だけで数十倍の大きさになります。これがML-DSA導入時の最大の注意点で、後半の実験でも確認します。

4. RFC 9964のポイント

RFC 9964は2026年5月に公開された標準化トラックのRFCで、ML-DSAの鍵と署名をJOSE(JWT、JWS、JWKなど)とCOSE(CBORベース、IoT向け)で扱う方法を定めています。ポイントは次の4つです。

(1) アルゴリズム名が登録された

JWTのヘッダーのalgに指定する名前が決まりました。

alg(JOSE) COSEの値
ML-DSA-44 -48
ML-DSA-65 -49
ML-DSA-87 -50

(2) 新しい鍵タイプ「AKP」

RSAはkty: RSA、楕円曲線はkty: ECでした。ML-DSAには新しくkty: AKP(Algorithm Key Pair)が定義されました。公開鍵はpub、秘密鍵はprivというパラメータに入ります。AKPはML-DSA専用ではなく、今後の別のアルゴリズムでも使える汎用の形式として設計されています。

{
  "kty": "AKP",
  "alg": "ML-DSA-44",
  "pub": "unH59k4Ru...(省略)",
  "priv": "AAAA...(省略)"
}

(3) 秘密鍵は32バイトのシードで表す

FIPS 204では秘密鍵の表し方が「シード」と「シードから展開した秘密鍵」の2通りあります。RFC 9964はコンパクトな32バイトのシードだけを採用し、相互運用性を優先しています。

(4) 事前ハッシュ版(HashML-DSA)は使わない

FIPS 204にはメッセージを先にハッシュしてから署名するHashML-DSAもありますが、RFC 9964では対象外です。またコンテキスト文字列(ctx)は空文字列と決められています。

RFC自身も、鍵と署名が大きいことは帯域・メモリ・処理能力が限られた環境で課題になり得ると注意書きしています。

5. やってみた:OpenSSLでML-DSAを動かす

OpenSSLは3.5(2025年4月リリース)からML-DSAに標準対応しています。今回はOpenSSL 3.5.4をソースからビルドして試しました。

  • 環境:Ubuntu 24.04、Intel Xeon 2.10GHz(1 vCPU の仮想マシン)
  • OpenSSL 3.5.4(Ubuntu 24.04標準の3.0系はML-DSA非対応なので注意)

5-1. 鍵生成・署名・検証

コマンドはECDSAとほとんど同じです。ML-DSAは「メッセージをそのまま渡す」ワンショット型の署名なので、-rawinを付けます。

# 署名するメッセージ
echo -n '{"sub":"device-001","kwh":12.5}' > msg.txt

# 鍵生成(ML-DSA-44 / ML-DSA-65 / ML-DSA-87 から選択)
openssl genpkey -algorithm ML-DSA-44 -out mldsa44.key
openssl pkey -in mldsa44.key -pubout -out mldsa44.pub

# 署名
openssl pkeyutl -sign -rawin -inkey mldsa44.key -in msg.txt -out mldsa44.sig

# 検証
openssl pkeyutl -verify -rawin -pubin -inkey mldsa44.pub -in msg.txt -sigfile mldsa44.sig
# => Signature Verified Successfully

ハマりどころが1つありました。-inを省略して標準入力から渡すと、unable to determine file size for oneshot operationというエラーになります。ワンショット署名ではメッセージ全体を先に読む必要があるため、ファイルで渡しましょう。

5-2. サイズ比較

実際に生成したファイルのサイズです(公開鍵はDER形式)。

方式 公開鍵(バイト) 署名(バイト)
ECDSA P-256 91 71
ML-DSA-44 1,334 2,420
ML-DSA-65 1,974 3,309
ML-DSA-87 2,614 4,627

ML-DSA-44でも署名はECDSAの約34倍です。公開鍵のDERは、RFCの表の値にアルゴリズム識別子分の22バイトが足されたサイズになっています。

5-3. 速度比較

OpenSSLのAPI(EVP_DigestSign / EVP_DigestVerify)を呼ぶ小さなCプログラムで、2秒間に何回署名・検証できるかを測りました。

方式 署名(回/秒) 検証(回/秒)
ECDSA P-256 36,152 12,178
Ed25519 29,265 8,791
RSA-2048 2,769 45,683
ML-DSA-44 1,721 8,700
ML-DSA-65 1,045 5,640
ML-DSA-87 941 3,603

検証はEd25519並みで、実用上の問題はなさそうです。署名は今回の環境ではECDSAより一桁以上遅い結果でした。ML-DSAの実装は最適化が進んでいる最中なので、CPUやライブラリのバージョンで大きく変わる点に注意してください。1 vCPUの仮想マシンでの参考値です。

5-4. ML-DSAでJWTを作ってみる

RFC 9964に沿って、algをML-DSA-44にしたJWTを作るPythonスクリプトです。署名部分はOpenSSLに任せています。

import base64, json, os, subprocess, sys, tempfile

OPENSSL = os.environ.get("OPENSSL", "openssl")

def b64url(b: bytes) -> str:
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

alg, key = sys.argv[1], sys.argv[2]   # 例: ML-DSA-44 mldsa44.key
header = {"alg": alg, "typ": "JWT"}
payload = {"sub": "device-001", "iat": 1761264000, "kwh": 12.5}
signing_input = (b64url(json.dumps(header, separators=(",", ":")).encode()) + "."
                 + b64url(json.dumps(payload, separators=(",", ":")).encode()))

# ML-DSAはワンショット署名のため、入力はファイルで渡す
with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(signing_input.encode())
sig = subprocess.run([OPENSSL, "pkeyutl", "-sign", "-rawin", "-inkey", key, "-in", f.name],
                     capture_output=True, check=True).stdout
print(signing_input + "." + b64url(sig))

同じペイロードで作ったJWTの長さを比べると、次のようになりました。

alg JWTの長さ(文字)
ES256(ECDSA P-256) 188
ML-DSA-44 3,335
ML-DSA-65 4,520
ML-DSA-87 6,278

中身はほとんど署名です。ES256の188文字に対して、ML-DSA-44は約18倍になりました。検証側も同様にスクリプトで確認でき、別の公開鍵で検証するとSignature Verification Failureになることも確かめました。

6. 実務への影響

実験してみて、一番効いてくるのは速度より「サイズ」だと感じました。

6-1. Web・APIの場合

  • Cookieに入らない可能性:ブラウザが保証するCookieの大きさは1つあたり約4KBです。ML-DSA-44のJWT(約3.3KB)はぎりぎり、ML-DSA-65以上は入りません。
  • HTTPヘッダーの上限:Authorization: BearerでJWTを送る場合、リバースプロキシやWebサーバーのヘッダー上限(8KB前後が多い)に近づきます。ML-DSA-87(約6.3KB)だと余裕がありません。
  • JWKSが大きくなる:公開鍵1つが数KBになるため、鍵ローテーションで複数の鍵を並べるとJWKSのレスポンスも大きくなります。RFC 9964でも、kid(サムプリント)を使って公開鍵の繰り返しを減らすことが推奨されています。

6-2. IoT・エネルギー機器の場合

個人的に一番気になったのがこちらです。蓄電池、EV充電器、ヒートポンプ給湯機、スマートメーターなどの分散型エネルギー機器は、10年以上現場で使われ続けます。VPP(仮想発電所)のように多数の機器を遠隔で制御する仕組みでは、「制御指令が本物か」「計測値が改ざんされていないか」を署名や証明書で担保しており、PQCの影響を直接受けます。

分散型エネルギー機器でよく使われる通信プロトコルと、PQC移行で気になる点を整理しました。

プロトコル 主な用途 現在の安全性の担保 PQC移行で気になる点
ECHONET Lite 家庭内の機器制御(HEMS) 主に下位層(Wi-Fi、Wi-SUNなど)の暗号化に依存 機器側の処理能力が小さく、大きな署名の検証が負担になりやすい
OpenADR 2.0 電力会社・アグリゲーターからのDR指令 TLSとX.509証明書(PKI) 証明書チェーンが大きくなり、TLSのハンドシェイクが重くなる
IEEE 2030.5 機器・ゲートウェイの遠隔管理(海外中心) TLSとECDSA(P-256)の機器証明書 長期間有効な機器証明書の入れ替えをどう行うか
スマートメーター(Bルート) 家庭での電力使用量の取得 Wi-SUNの暗号化と認証 1フレームが小さく、大きな署名を載せにくい

そのうえで、具体的には次の3点が課題になりそうです。

  • 通信帯域:Wi-SUNやLPWAなど1パケットが数十〜数百バイトの通信では、2〜5KBの署名は1回で送れません。30分値などの計測データに毎回署名をつけると通信量が大きく増えるため、複数の計測値をまとめて署名する、ゲートウェイで署名するなどの設計見直しが必要です。
  • 機器のリソース:ML-DSAは計算に使うメモリも大きめです。蓄電池やEV充電器の制御基板がマイコンクラスの場合、ライブラリが載るか、署名処理の時間が許容範囲かを事前に確認する必要があります。DRのように応答時間に要件がある制御では特に重要です。
  • 寿命の長さ:今出荷する機器のファームウェア署名や機器証明書は、量子コンピュータが実用化される時期まで使われる可能性があります。現場の機器を後から一斉に入れ替えるのは現実的ではないため、ファームウェア更新で署名アルゴリズムや証明書を差し替えられる設計にしておく価値は大きいです。

前々回の記事で扱った環境価値取引も同じ構図です。計測データや証書の署名は長期間検証され続けるので、PQCの影響を早めに受ける分野だと考えています。

6-3. 移行の進め方

今すぐ全部をML-DSAにする必要はありませんが、準備は始めておきたいところです。NISTの移行計画案(NIST IR 8547)では、従来の公開鍵暗号を2030年ごろから段階的に非推奨とし、2035年に原則廃止する方向が示されています。

  1. 棚卸し:どこでどの署名アルゴリズムを使っているかを洗い出す(JWT、証明書、ファームウェア署名など)
  2. 暗号アジリティの確保:アルゴリズムをハードコードせず、設定で差し替えられるようにする。検証側では受け付けるalgを許可リストで固定する
  3. 長く残る署名から優先:ファームウェア、長期保存する証書・記録など
  4. ライブラリの対応状況を確認:OpenSSL 3.5以降、使っているJWTライブラリのRFC 9964対応など
  5. サイズの影響を検証:今回のような実測で、通信やストレージへの影響を確認する

7. まとめ

  • 量子コンピュータに備えて、署名の世界ではML-DSA(FIPS 204)が主力になりつつある
  • RFC 9964で、JWTなどでML-DSAを使うためのalg名(ML-DSA-44/65/87)と鍵形式(AKP)が決まった
  • OpenSSL 3.5以降なら、コマンドはECDSAとほぼ同じ感覚で使える
  • 検証速度は十分だが、署名と公開鍵が数十倍大きくなる点が最大の注意点
  • 特に長く使われるIoT・エネルギー機器は、今からアルゴリズムを差し替えられる設計にしておきたい

次回は、前回予告したもう一方の「AI×ネットワーク運用」を深掘りしてみたいと思います。

参考資料

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?