量子コンピュータによって、現在のSSHやTLS通信が突然すべて解読される。

このような話を聞いても、多くの企業では、まだ現実味を感じられないかもしれません。

量子コンピュータは、現在の一般的なサーバーへ攻撃を行い、RSAや楕円曲線暗号を即座に破れる段階には到達していません。

それなら、耐量子暗号への対応は、量子コンピュータが完成してから考えればよいのでしょうか。

実際には、それでは遅い可能性があります。

攻撃者は、現在のSSH、TLS、VPNなどの暗号化通信を保存し、将来、十分に強力な量子コンピュータが利用できるようになった時点で復号する可能性があります。

この攻撃は、次のように呼ばれています。

  • Harvest Now, Decrypt Later
  • Store Now, Decrypt Later
  • 今収集し、後で復号する攻撃

例えば、2026年に送受信した次の情報が、2030年代に復号される可能性を考える必要があります。

  • SSH経由で実行した管理コマンド
  • サーバー設定ファイル
  • データベースのバックアップ
  • 顧客情報
  • 製造条件や原価情報
  • 新製品の設計情報
  • 経営資料
  • APIキーや認証情報

情報の価値が数年間で消えるのであれば、将来復号されても大きな問題にならないかもしれません。

しかし、顧客情報、知的財産、製造ノウハウ、長期間利用する秘密鍵などは、10年後も価値を持つ可能性があります。

そのため、量子コンピュータが完成する正確な年を予測するよりも、現在の通信が耐量子鍵交換を利用できる状態かを、今から把握することが重要です。

そこで利用できるのが、Anvil Secureが公開しているオープンソースツールPQCScanです。

PQCScanを利用すると、SSHサーバーとTLSサーバーに対して、耐量子暗号または耐量子暗号を含むハイブリッド鍵交換方式が利用可能かを調査できます。

結果はJSON形式で保存でき、複数サーバーの結果を一つのHTMLレポートへまとめることもできます。

本記事では、PQCScanを紹介するだけではなく、実際に企業で利用できるレベルまで踏み込みます。

  • 耐量子暗号とハイブリッド暗号の違い
  • OpenSSHの耐量子対応状況
  • PQCScanのUbuntuへの導入
  • macOS・Windowsでの利用方法
  • SSHサーバーの単体検査
  • TLSサーバーの単体検査
  • 社内サーバーの一括検査
  • JSON結果の読み方
  • HTML監査レポートの作成
  • CSV台帳への変換
  • OpenSSH設定の改善方法
  • CDN配下とオリジンサーバーの違い
  • systemdによる定期検査
  • CI/CDでの耐量子対応チェック
  • PQCScanでは確認できない範囲

本記事のスキャンは、自社が所有・管理するサーバー、または検査許可を得たシステムだけを対象にしてください。


目次

PQCScanとは

PQCScanは、Anvil Secureが開発・公開している、Post-Quantum Cryptography対応状況の検査ツールです。

公式GitHubリポジトリ:

anvilsecure/pqcscan

PQCScanはRustで実装されており、Linux、macOS、Windows向けのバイナリが配布されています。

主な機能は、次の三つです。

  1. SSHサーバーのPQC対応状況を検査する
  2. TLSサーバーのPQC対応状況を検査する
  3. 複数のJSON結果からHTMLレポートを作る

使用するサブコマンドは次のとおりです。

pqcscan ssh-scan
pqcscan tls-scan
pqcscan create-report

2026年7月20日の確認時点で、GitHub上の最新リリースとして表示されているのは0.8.0です。

0.8.0では、処理の最適化に加えて、TLS検査時に従来型の非PQCアルゴリズムも調べる–test-nonpqc-algosオプションが追加されています。


PQCScanで分かること

PQCScanは、対象サーバーへSSHまたはTLSの接続要求を送り、どの鍵交換方式を受け入れるかを確認します。

SSH検査で分かること

  • 対象SSHサーバーへ接続できるか
  • 耐量子対応の鍵交換方式を提示しているか
  • 対応しているPQC系KEXアルゴリズム
  • 対応している従来型KEXアルゴリズム
  • 名前解決後の接続先IPアドレス
  • 接続失敗やタイムアウト

TLS検査で分かること

  • 対象TLSサーバーへ接続できるか
  • 純粋なPQCグループを受け入れるか
  • ハイブリッドPQCグループを受け入れるか
  • 従来型グループを受け入れるか
  • どのTLS鍵交換グループが利用可能か
  • 接続先IPアドレス
  • 接続失敗やTLSアラート

結果として保存される主な項目

SSHの結果には、概ね次の情報が含まれます。

{
  "targetspec": {
    "host": "server.example.com",
    "port": 22
  },
  "addr": "192.0.2.10:22",
  "error": null,
  "pqc_supported": true,
  "pqc_algos": [
    "mlkem768x25519-sha256",
    "sntrup761x25519-sha512"
  ],
  "nonpqc_algos": [
    "curve25519-sha256"
  ]
}

TLSの結果では、PQCとハイブリッドPQCが分けて保存されます。

{
  "targetspec": {
    "host": "www.example.com",
    "port": 443
  },
  "addr": "192.0.2.20:443",
  "error": null,
  "pqc_supported": true,
  "pqc_algos": [],
  "hybrid_algos": [
    "X25519MLKEM768"
  ],
  "nonpqc_algos": [
    "X25519",
    "secp256r1"
  ]
}

上記は構造を説明するために値を簡略化した例です。実際のアルゴリズム名や結果は、PQCScanのバージョンと対象サーバーによって異なります。


PQCScanだけでは分からないこと

PQCScanは非常に分かりやすいツールですが、結果を過大評価してはいけません。

PQCScanが確認するのは、主に鍵交換方式の対応状況です。

次の内容を完全に監査するツールではありません。

  • SSHホスト鍵の署名方式が耐量子か
  • TLS証明書の署名方式が耐量子か
  • VPNが耐量子暗号に対応しているか
  • IPsecが耐量子暗号に対応しているか
  • データベース内部の暗号化方式
  • バックアップファイルの暗号化方式
  • メールのS/MIMEやPGP
  • コード署名
  • ファームウェア署名
  • 保存データの暗号化
  • サーバーに脆弱性が存在するか

PQCScanでpqc_supported: trueと表示されても、システム全体が完全に耐量子化されたわけではありません。

正しくは、次のように解釈します。

対象のSSHまたはTLS接続先が、少なくとも一つの耐量子・ハイブリッド鍵交換方式を受け入れた。


耐量子暗号とは何か

現在広く利用されている公開鍵暗号の多くは、特定の数学的問題を古典コンピュータで解くことが非常に難しいという前提で安全性を保っています。

代表例は次のとおりです。

  • RSA
  • Diffie-Hellman
  • ECDH
  • ECDSA

十分に大規模な量子コンピュータが実現した場合、Shorのアルゴリズムによって、これらの安全性の基盤となる問題が効率的に解かれる可能性があります。

耐量子暗号、Post-Quantum Cryptography、PQCは、古典コンピュータで実行でき、かつ量子コンピュータによる攻撃にも耐えられると考えられている暗号方式です。

NISTは2024年8月13日、最初の耐量子暗号標準を正式公開しました。

  • FIPS 203:ML-KEM
  • FIPS 204:ML-DSA
  • FIPS 205:SLH-DSA

PQCScanで特に重要になるのが、FIPS 203のML-KEMです。


ML-KEMとは

ML-KEMは、Module-Lattice-Based Key-Encapsulation Mechanismの略です。

以前はCRYSTALS-Kyberという名称で知られていました。

KEMは、二者が安全な共有秘密を確立するための仕組みです。

その共有秘密から、実際の通信内容を暗号化する共通鍵を生成します。

NIST FIPS 203では、次の三つのパラメータセットが定義されています。

  • ML-KEM-512
  • ML-KEM-768
  • ML-KEM-1024

数字が大きいほどセキュリティ強度が高くなる一方、鍵や暗号文のサイズ、計算量も増加します。

SSHとTLSでは、ML-KEM単体だけでなく、従来型の鍵交換方式と組み合わせたハイブリッド方式が多く利用されています。


なぜハイブリッド方式を使うのか

新しい暗号アルゴリズムには、将来、未知の弱点が発見される可能性があります。

耐量子暗号だけへ一気に置き換えた後、その方式に重大な問題が見つかれば、通信の安全性を失う可能性があります。

そこで、現在実績のある従来型暗号と、耐量子暗号の両方を組み合わせます。

代表例は次のとおりです。

mlkem768x25519-sha256

この方式は、次の二つを組み合わせます。

  • ML-KEM-768:耐量子鍵カプセル化
  • X25519:従来型の楕円曲線鍵交換

仮にML-KEMへ未知の問題が見つかっても、X25519の強度を維持できます。

逆に、将来の量子コンピュータがX25519を破れるようになっても、ML-KEMが通信を守ります。

OpenSSH公式は、このようなハイブリッド構成を採用しています。


OpenSSHはすでに耐量子鍵交換を標準利用している

OpenSSHは、量子コンピュータの脅威を待たず、かなり早い段階から耐量子鍵交換を導入してきました。

OpenSSH 9.0

2022年4月に公開されたOpenSSH 9.0以降では、次のハイブリッド方式が標準的に利用されています。

sntrup761x25519-sha512

実装やバージョンによっては、次のベンダー拡張名で表示されます。

sntrup761x25519-sha512@openssh.com

OpenSSH 9.9

OpenSSH 9.9では、NIST標準のML-KEMとX25519を組み合わせた方式が追加されました。

mlkem768x25519-sha256

OpenSSH 10.0

OpenSSH 10.0では、mlkem768x25519-sha256が既定の鍵交換方式になりました。

OpenSSH 10.1

OpenSSH 10.1では、耐量子鍵交換を利用しない接続に対し、次のような警告を表示する機能が追加されています。

WARNING: connection is not using a post-quantum key exchange algorithm.
This session may be vulnerable to "store now, decrypt later" attacks.

つまり、SSHの耐量子対応は、将来の実験的機能ではありません。

すでに標準のOpenSSHへ組み込まれ、通常利用される段階に入っています。


各国の移行計画は「まず棚卸し」から始まっている

英国NCSCは、耐量子暗号への移行について次の目標を示しています。

  • 2028年まで:暗号を利用するシステムを全面的に把握し、移行計画を作る
  • 2031年まで:優先度の高いシステムを移行し、計画を具体化する
  • 2035年まで:すべてのシステム、サービス、製品の移行を完了する

重要なのは、2028年までにすべてを耐量子化するという意味ではない点です。

最初に必要なのは、現在どこで、どの暗号方式を使っているかを把握することです。

PQCScanは、SSHとTLSという限定された範囲ではありますが、この暗号資産棚卸しを始めるための実用的なツールです。


PQCScanを導入する環境

PQCScanは、検査対象サーバーへインストールする必要はありません。

ネットワーク経由でSSHまたはTLS接続できる管理端末へ、一つインストールすれば利用できます。

推奨環境

  • Ubuntu 22.04または24.04
  • macOS
  • Windows 11
  • Rust 1.80以降相当の現在の安定版
  • 社内検査対象へ接続できるネットワーク

検査用端末を分ける

企業で継続的に利用する場合は、個人PCではなく、専用の監査用Linuxサーバーを用意すると管理しやすくなります。

監査用サーバーには、次の権限は不要です。

  • SSHログイン用パスワード
  • 秘密鍵
  • root権限
  • Webサーバー管理者権限

PQCScanは、暗号アルゴリズムの交渉を行うだけなので、対象SSHサーバーへログインする必要はありません。


方法1:配布済みバイナリを利用する

PQCScanのGitHub Releasesでは、Linux、macOS、Windows向けのビルド済みバイナリが配布されています。

公式リリース:

PQCScan Releases

自分のOSとCPUアーキテクチャに合うファイルをダウンロードします。

Linuxで展開した例:

mkdir -p ~/tools/pqcscan
cd ~/tools/pqcscan

unzip pqcscan-*.zip

chmod +x pqcscan

./pqcscan --version
./pqcscan --help

システム全体で使う場合:

sudo install -m 0755 pqcscan /usr/local/bin/pqcscan

pqcscan --version

配布ファイルにチェックサムや署名が用意されている場合は、実行前に必ず検証してください。


方法2:Ubuntuでソースコードからビルドする

ソースコードを確認してから利用したい場合や、自社環境で再ビルドしたい場合は、Rustでコンパイルします。

必要パッケージを導入する

sudo apt update

sudo apt install -y \
  build-essential \
  pkg-config \
  libssl-dev \
  git \
  curl

Rustを導入する

Rust公式のrustupを利用します。

実行前にスクリプトを確認する場合:

curl --proto '=https' \
  --tlsv1.2 \
  -sSf \
  https://sh.rustup.rs \
  | less

内容を確認後、導入します。

curl --proto '=https' \
  --tlsv1.2 \
  -sSf \
  https://sh.rustup.rs \
  | sh

環境変数を反映します。

source "$HOME/.cargo/env"

rustc --version
cargo --version

PQCScanを取得する

mkdir -p "$HOME/src"
cd "$HOME/src"

git clone https://github.com/anvilsecure/pqcscan.git
cd pqcscan

取得したコミットを記録します。

git rev-parse HEAD
git log -1 --oneline

リリースビルドする

cargo build --release

完了すると、次の場所に実行ファイルが作成されます。

./target/release/pqcscan

動作確認します。

./target/release/pqcscan --version
./target/release/pqcscan --help

/usr/local/binへ配置する

sudo install \
  -m 0755 \
  ./target/release/pqcscan \
  /usr/local/bin/pqcscan

確認します。

which pqcscan
pqcscan --version
pqcscan --help

macOSへ導入する

macOSでは、GitHub Releasesのバイナリを使う方法が簡単です。

ソースからビルドする場合は、Command Line Toolsを導入します。

xcode-select --install

Rustを導入します。

curl --proto '=https' \
  --tlsv1.2 \
  -sSf \
  https://sh.rustup.rs \
  | sh

source "$HOME/.cargo/env"

その後、Ubuntuと同様にビルドします。

git clone https://github.com/anvilsecure/pqcscan.git
cd pqcscan

cargo build --release

./target/release/pqcscan --help

ユーザー用コマンドとして配置する場合:

mkdir -p "$HOME/.local/bin"

cp ./target/release/pqcscan \
  "$HOME/.local/bin/pqcscan"

Windowsへ導入する

Windowsでは、GitHub ReleasesからWindows向けバイナリを取得する方法が簡単です。

PowerShellで展開後、実行します。

.\pqcscan.exe --version
.\pqcscan.exe --help

任意の場所から実行できるようにする場合は、PQCScanを配置したディレクトリをPATHへ追加します。

Windowsから社内SSH・TLSサーバーへ到達できることを確認してください。

Test-NetConnection 192.168.1.5 -Port 22
Test-NetConnection www.example.com -Port 443

最初に自分自身のSSHサーバーを検査する

Ubuntuの監査端末自身でsshdが動作している場合、localhostを検査できます。

pqcscan ssh-scan \
  -t 127.0.0.1:22 \
  -o local-ssh.json

ホスト名だけを指定した場合、SSHでは既定ポート22が利用されます。

pqcscan ssh-scan \
  -t localhost \
  -o local-ssh.json

ログを詳しく確認する場合:

RUST_LOG=debug \
pqcscan ssh-scan \
  -t 127.0.0.1:22 \
  -o local-ssh.json

さらに詳細な通信ログを確認する場合:

RUST_LOG=trace \
pqcscan ssh-scan \
  -t 127.0.0.1:22 \
  -o local-ssh-trace.json

traceは大量のログが出るため、通常はdebugで十分です。


社内UbuntuサーバーをSSH検査する

例えば、次のサーバーを検査します。

192.168.1.5:22

コマンド:

pqcscan ssh-scan \
  -t 192.168.1.5:22 \
  -o server-192-168-1-5-ssh.json

SSHが2222番ポートの場合:

pqcscan ssh-scan \
  -t 49.212.185.13:2222 \
  -o server-external-ssh.json

PQCScanはログイン処理を行わないため、SSHユーザー名、パスワード、秘密鍵は不要です。


SSH検査結果をjqで確認する

JSON全体を整形表示します。

jq . server-192-168-1-5-ssh.json

SSH結果だけを見やすく抽出します。

jq '
  .results[]
  | select(.Ssh != null)
  | {
      target: .Ssh.targetspec,
      address: .Ssh.addr,
      error: .Ssh.error,
      pqc_supported: .Ssh.pqc_supported,
      pqc_algorithms: .Ssh.pqc_algos,
      non_pqc_algorithms: .Ssh.nonpqc_algos
    }
' server-192-168-1-5-ssh.json

PQC対応しているかだけを確認します。

jq -r '
  .results[]
  | .Ssh.pqc_supported
' server-192-168-1-5-ssh.json

対応アルゴリズムだけを確認します。

jq -r '
  .results[]
  | .Ssh.pqc_algos[]
' server-192-168-1-5-ssh.json

OpenSSH自身のコマンドでも対応状況を確認する

PQCScanの結果を、OpenSSH自身の情報と照合します。

クライアントのOpenSSHバージョン

ssh -V

サーバーのOpenSSHバージョン

sudo /usr/sbin/sshd -V 2>&1

環境によってはsshd -Vが利用できない場合があります。

クライアントが対応する鍵交換方式

ssh -Q kex

耐量子系だけを抽出します。

ssh -Q kex \
  | grep -Ei 'mlkem|sntrup|kyber|ntru'

sshdが実際に有効化する鍵交換方式

sudo sshd -T \
  | grep '^kexalgorithms'

一行ずつ表示します。

sudo sshd -T \
  | awk '/^kexalgorithms / {print $2}' \
  | tr ',' '\n'

耐量子系だけを抽出します。

sudo sshd -T \
  | awk '/^kexalgorithms / {print $2}' \
  | tr ',' '\n' \
  | grep -Ei 'mlkem|sntrup|kyber|ntru'

実際にどのSSH鍵交換方式が選ばれたか確認する

対応しているだけではなく、実際の接続で選択された方式を確認します。

ssh -vv \
  user@server.example.com \
  true \
  2>&1 \
  | grep -E 'kex: algorithm|KEX algorithms'

期待する表示例:

debug1: kex: algorithm: mlkem768x25519-sha256

クライアントとサーバーの両方が対応していなければ、この方式は選ばれません。

指定した方式で接続できるかを試す場合:

ssh \
  -o KexAlgorithms=mlkem768x25519-sha256 \
  user@server.example.com

クライアントまたはサーバーが対応していない場合、接続できません。


OpenSSHがPQC非対応だった場合の確認手順

PQCScanでpqc_supported: falseとなった場合、すぐに設定ファイルへアルゴリズム名を書き込んではいけません。

まず、実行バイナリ自体が対応しているかを確認します。

1.OpenSSHバージョンを確認する

ssh -V
sudo /usr/sbin/sshd -V 2>&1

2.バイナリが対応するKEXを確認する

ssh -Q kex \
  | grep -Ei 'mlkem|sntrup'

3.設定で無効化されていないか確認する

sudo grep -RniE \
  '^[[:space:]]*KexAlgorithms' \
  /etc/ssh/sshd_config \
  /etc/ssh/sshd_config.d \
  2>/dev/null

古いセキュリティ設定で、許可するアルゴリズムを固定している場合、OSを更新しても新しい方式が利用されません。

例えば、次のような固定設定です。

KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256

この設定では、OpenSSHがML-KEMに対応していても候補から除外されます。

4.実効設定を確認する

sudo sshd -T \
  | grep '^kexalgorithms'

5.OS更新を優先する

古いUbuntuへ新しいOpenSSHを手作業で上書きすると、次の問題が起こる可能性があります。

  • PAM認証との不整合
  • systemd設定との不整合
  • パッケージ管理から外れる
  • 自動セキュリティ更新を受けられない
  • sshdのパスが変わる
  • 設定ファイルの配置が変わる
  • 接続不能になる

本番サーバーでは、手動コンパイルによる置き換えより、サポートされているOSバージョンへの更新を優先してください。


SSH設定を変更する際の安全手順

SSH設定を誤ると、リモート接続できなくなる可能性があります。

必ず現在のSSHセッションを開いたまま、別セッションで接続確認します。

バックアップ

sudo cp -a \
  /etc/ssh/sshd_config \
  "/etc/ssh/sshd_config.bak_$(date +%Y%m%d_%H%M%S)"

設定ファイルの構文確認

sudo sshd -t

何も表示されなければ、基本的な構文は正常です。

実効設定の確認

sudo sshd -T \
  | grep '^kexalgorithms'

再起動ではなくreloadする

Ubuntuでは、サービス名がsshの場合があります。

sudo systemctl reload ssh

環境によっては次です。

sudo systemctl reload sshd

別ターミナルから接続確認する

ssh -vv user@server.example.com

PQCScanで再検査する

pqcscan ssh-scan \
  -t server.example.com:22 \
  -o server-after-ssh.json

TLSサーバーを検査する

HTTPSサーバーを検査する場合は、tls-scanを使用します。

pqcscan tls-scan \
  -t www.example.com:443 \
  -o example-tls.json

ポートを省略した場合は、既定の443番が利用されます。

pqcscan tls-scan \
  -t www.example.com \
  -o example-tls.json

ハイブリッド方式だけを検査する

実運用で採用される可能性が高いハイブリッド方式だけを調べる場合:

pqcscan tls-scan \
  -t www.example.com:443 \
  --only-hybrid-algos \
  -o example-tls-hybrid.json

従来型アルゴリズムも同時に調べる

pqcscan tls-scan \
  -t www.example.com:443 \
  --test-nonpqc-algos \
  -o example-tls-all.json

詳細ログ

RUST_LOG=debug \
pqcscan tls-scan \
  -t www.example.com:443 \
  --test-nonpqc-algos \
  -o example-tls-debug.json

TLS検査結果をjqで確認する

jq '
  .results[]
  | select(.Tls != null)
  | {
      target: .Tls.targetspec,
      address: .Tls.addr,
      error: .Tls.error,
      pqc_supported: .Tls.pqc_supported,
      pure_pqc: .Tls.pqc_algos,
      hybrid_pqc: .Tls.hybrid_algos,
      non_pqc: .Tls.nonpqc_algos
    }
' example-tls-all.json

ハイブリッドPQCだけを表示します。

jq -r '
  .results[]
  | .Tls.hybrid_algos[]
' example-tls-all.json

対応・非対応だけを表示します。

jq -r '
  .results[]
  | "\(.Tls.targetspec.host):\(.Tls.targetspec.port) \(.Tls.pqc_supported)"
' example-tls-all.json

CDN配下のドメインを検査する際の注意

WebサイトがCloudflare、Fastly、Akamai、AWS CloudFrontなどのCDNやリバースプロキシを利用している場合、PQCScanが確認するのは、原則としてインターネット側の接続先です。

次の構成を考えます。

ブラウザ
  ↓ TLS
CDN
  ↓ TLS
オリジンサーバー

公開ドメインを検査してPQC対応になっていても、CDNからオリジンサーバーまでの通信がPQC対応しているとは限りません。

逆に、オリジンサーバーがPQC非対応でも、利用者からCDNまでの通信だけはPQC対応している可能性があります。

可能であれば、次の二箇所を分けて確認します。

  • 公開ドメイン
  • オリジンサーバーの直接接続先

オリジンを直接インターネットへ公開していない場合は、社内またはVPN内の監査端末から検査します。

pqcscan tls-scan \
  -t www.example.com:443 \
  -o public-edge.json

pqcscan tls-scan \
  -t origin.internal.example:443 \
  -o private-origin.json

両方を一つのレポートへまとめます。

pqcscan create-report \
  -i public-edge.json private-origin.json \
  -o edge-origin-comparison.html

複数サーバーを一括検査する

PQCScanでは、ターゲット一覧ファイルを-Tで指定できます。

SSHとTLSは、別々の一覧に分けると管理しやすくなります。

SSH一覧

mkdir -p ~/pqc-audit
cd ~/pqc-audit

cat > ssh-targets.txt <<'EOF'
# 社内基幹サーバー
192.168.1.5:22
192.168.1.7:22

# 外部VPS
server1.example.com:22
server2.example.com:2222

# NAS
192.168.1.3:22
EOF

空行と、#から始まるコメント行は無視されます。

TLS一覧

cat > tls-targets.txt <<'EOF'
# 公開Webサイト
www.example.com:443
system.example.com:443

# 社内システム
192.168.1.5:443
192.168.1.7:443

# 異なるポート
internal.example.com:8443
EOF

SSHを一括検査する

pqcscan ssh-scan \
  -T ssh-targets.txt \
  --num-threads 8 \
  -o ssh-results.json

TLSを一括検査する

pqcscan tls-scan \
  -T tls-targets.txt \
  --num-threads 8 \
  --only-hybrid-algos \
  -o tls-results.json

従来型も含めた詳細検査

pqcscan tls-scan \
  -T tls-targets.txt \
  --num-threads 8 \
  --test-nonpqc-algos \
  -o tls-results-full.json

既定のスレッド数は8です。

ターゲット数がスレッド数より少ない場合は、内部でターゲット数に合わせて調整されます。


スキャン時間の考え方

現在のPQCScanソースでは、接続タイムアウトは5秒、読み取りタイムアウトは10秒に設定されています。

到達不能なIPアドレスが多数含まれていると、検査に時間がかかる可能性があります。

最初に疎通確認を行うと効率的です。

SSH疎通確認

nc -vz 192.168.1.5 22

TLS疎通確認

nc -vz www.example.com 443

所有するネットワークのポート確認

自社管理ネットワークだけを対象にします。

nmap -sT \
  -p 22,443,2222,8443 \
  --open \
  192.168.1.0/24

Nmapの結果を、そのまま無差別にPQCScanへ渡すのではなく、資産台帳と照合してください。


HTML監査レポートを作成する

SSHとTLSのJSON結果を、一つのHTMLへまとめます。

pqcscan create-report \
  -i ssh-results.json tls-results.json \
  -o pqc-report.html

macOSで開く場合:

open pqc-report.html

Linuxデスクトップの場合:

xdg-open pqc-report.html

Windows PowerShellの場合:

Start-Process .\pqc-report.html

HTMLレポートには、概ね次の内容が表示されます。

  • SSH対象数
  • TLS対象数
  • スキャン成功数
  • スキャン失敗数
  • PQC対応数
  • 対象ホスト別の結果
  • 対応アルゴリズム
  • スキャン実施時間帯

異なるバージョンのJSONは混在させない

PQCScanのレポート生成処理は、JSONへ記録されたPQCScanバージョンを確認します。

入力JSONのバージョンが現在のPQCScanと異なる場合、バージョン不一致としてレポート作成に失敗することがあります。

定期監査では、次を記録してください。

  • PQCScanのバージョン
  • 使用したターゲット一覧
  • 実施日時
  • 実行コマンド

複数JSONをCSV台帳へ変換する

企業で利用する場合、HTMLだけでなくCSVへ変換すると、Excelや資産管理システムへ取り込みやすくなります。

次のPythonスクリプトは、PQCScanのSSH・TLS JSONを読み込み、CSVへ変換します。

cat > pqcscan_to_csv.py <<'PY'
#!/usr/bin/env python3

from __future__ import annotations

import argparse
import csv
import json
import sys
from pathlib import Path
from typing import Any, Iterable


FIELDNAMES = [
    "source_file",
    "protocol",
    "host",
    "port",
    "resolved_address",
    "scan_error",
    "pqc_supported",
    "pqc_algorithms",
    "hybrid_algorithms",
    "non_pqc_algorithms",
    "scan_start",
    "scan_end",
    "pqcscan_version",
]


def join_values(value: Any) -> str:
    if not value:
        return ""
    if isinstance(value, list):
        return ";".join(str(item) for item in value)
    return str(value)


def load_rows(path: Path) -> Iterable[dict[str, Any]]:
    with path.open("r", encoding="utf-8") as handle:
        document = json.load(handle)

    start_time = document.get("start_time", "")
    end_time = document.get("end_time", "")
    version = document.get("version", "")

    for item in document.get("results", []):
        if "Ssh" in item:
            result = item["Ssh"]
            protocol = "SSH"
            hybrid_algorithms = []
        elif "Tls" in item:
            result = item["Tls"]
            protocol = "TLS"
            hybrid_algorithms = result.get("hybrid_algos") or []
        else:
            continue

        target = result.get("targetspec") or {}

        yield {
            "source_file": str(path),
            "protocol": protocol,
            "host": target.get("host", ""),
            "port": target.get("port", ""),
            "resolved_address": result.get("addr") or "",
            "scan_error": result.get("error") or "",
            "pqc_supported": bool(result.get("pqc_supported", False)),
            "pqc_algorithms": join_values(
                result.get("pqc_algos")
            ),
            "hybrid_algorithms": join_values(
                hybrid_algorithms
            ),
            "non_pqc_algorithms": join_values(
                result.get("nonpqc_algos")
            ),
            "scan_start": start_time,
            "scan_end": end_time,
            "pqcscan_version": version,
        }


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Convert PQCScan JSON files to CSV."
    )

    parser.add_argument(
        "json_files",
        nargs="+",
        type=Path,
        help="PQCScan JSON files",
    )

    parser.add_argument(
        "-o",
        "--output",
        type=Path,
        required=True,
        help="Output CSV file",
    )

    parser.add_argument(
        "--fail-on-unsupported",
        action="store_true",
        help="Exit with status 2 if a reachable target lacks PQC.",
    )

    args = parser.parse_args()

    rows: list[dict[str, Any]] = []

    for path in args.json_files:
        try:
            rows.extend(load_rows(path))
        except (OSError, json.JSONDecodeError) as exc:
            print(
                f"ERROR: Failed to read {path}: {exc}",
                file=sys.stderr,
            )
            return 1

    args.output.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    with args.output.open(
        "w",
        encoding="utf-8-sig",
        newline="",
    ) as handle:
        writer = csv.DictWriter(
            handle,
            fieldnames=FIELDNAMES,
        )

        writer.writeheader()
        writer.writerows(rows)

    reachable = [
        row
        for row in rows
        if not row["scan_error"]
    ]

    unsupported = [
        row
        for row in reachable
        if not row["pqc_supported"]
    ]

    failed = [
        row
        for row in rows
        if row["scan_error"]
    ]

    print(
        f"Total={len(rows)} "
        f"Reachable={len(reachable)} "
        f"PQCUnsupported={len(unsupported)} "
        f"ScanFailed={len(failed)}",
        file=sys.stderr,
    )

    if unsupported:
        print(
            "Targets without detected PQC support:",
            file=sys.stderr,
        )

        for row in unsupported:
            print(
                f"- {row['protocol']} "
                f"{row['host']}:{row['port']}",
                file=sys.stderr,
            )

    if args.fail_on_unsupported and unsupported:
        return 2

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
PY

chmod +x pqcscan_to_csv.py

実行します。

python3 pqcscan_to_csv.py \
  ssh-results.json \
  tls-results.json \
  -o pqc-inventory.csv

Windows版Excelで文字化けしにくいように、UTF-8 BOM付きで出力します。

PQC非対応があれば終了コード2にする

python3 pqcscan_to_csv.py \
  ssh-results.json \
  tls-results.json \
  -o pqc-inventory.csv \
  --fail-on-unsupported

CI/CDや監視処理では、終了コード2を検出し、管理者へ通知できます。


検査結果の優先度を分類する

すべてのPQC非対応システムを、同じ優先度で扱う必要はありません。

優先度 対象例 対応
最優先 インターネット公開SSH、顧客情報、機密通信 早期に更新計画を作る
VPN経由の基幹サーバー、バックアップサーバー OS・SSH・TLS基盤を更新
社内Webシステム、監視サーバー 更新時期を資産台帳へ登録
短期間で廃止予定の検証環境 廃止計画と整合させる
要調査 接続失敗、ポート閉鎖、名前解決失敗 資産の存在と用途を確認

特に注意すべきなのは、スキャン失敗をPQC非対応と同一視しないことです。

次の理由で失敗する可能性があります。

  • ファイアウォール
  • 接続元IP制限
  • サービス停止
  • 名前解決失敗
  • ポート番号の誤り
  • 接続タイムアウト
  • TLS中継機器の特殊動作

週次監査を自動化する

耐量子対応状況は、OS更新、ロードバランサー変更、CDN設定変更などで変化します。

一度だけ検査するのではなく、定期的に実行して差分を残します。

専用ユーザーを作る

sudo useradd \
  --system \
  --home /var/lib/pqcscan \
  --create-home \
  --shell /usr/sbin/nologin \
  pqcscan

設定と結果用ディレクトリを作る

sudo install \
  -d \
  -o pqcscan \
  -g pqcscan \
  -m 0750 \
  /etc/pqcscan \
  /var/lib/pqcscan \
  /var/lib/pqcscan/results

ターゲット一覧を配置する

sudo cp ssh-targets.txt \
  /etc/pqcscan/ssh-targets.txt

sudo cp tls-targets.txt \
  /etc/pqcscan/tls-targets.txt

sudo chown \
  root:pqcscan \
  /etc/pqcscan/*.txt

sudo chmod \
  0640 \
  /etc/pqcscan/*.txt

実行スクリプトを作る

sudo tee \
  /usr/local/sbin/run-pqcscan-audit.sh \
  >/dev/null <<'EOF'
#!/usr/bin/env bash

set -euo pipefail

RESULT_ROOT="/var/lib/pqcscan/results"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
RESULT_DIR="${RESULT_ROOT}/${STAMP}"

mkdir -p "$RESULT_DIR"

pqcscan ssh-scan \
  -T /etc/pqcscan/ssh-targets.txt \
  --num-threads 8 \
  -o "${RESULT_DIR}/ssh.json"

pqcscan tls-scan \
  -T /etc/pqcscan/tls-targets.txt \
  --num-threads 8 \
  --test-nonpqc-algos \
  -o "${RESULT_DIR}/tls.json"

pqcscan create-report \
  -i \
  "${RESULT_DIR}/ssh.json" \
  "${RESULT_DIR}/tls.json" \
  -o "${RESULT_DIR}/report.html"

ln -sfn \
  "$RESULT_DIR" \
  "${RESULT_ROOT}/latest"

find "$RESULT_ROOT" \
  -mindepth 1 \
  -maxdepth 1 \
  -type d \
  -mtime +180 \
  -exec rm -rf {} +
EOF

sudo chmod \
  0755 \
  /usr/local/sbin/run-pqcscan-audit.sh

systemdサービスを作る

sudo tee \
  /etc/systemd/system/pqcscan-audit.service \
  >/dev/null <<'EOF'
[Unit]
Description=Post-Quantum SSH/TLS Audit
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=pqcscan
Group=pqcscan

ExecStart=/usr/local/sbin/run-pqcscan-audit.sh

NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true

ReadOnlyPaths=/etc/pqcscan
ReadWritePaths=/var/lib/pqcscan

[Install]
WantedBy=multi-user.target
EOF

systemdタイマーを作る

sudo tee \
  /etc/systemd/system/pqcscan-audit.timer \
  >/dev/null <<'EOF'
[Unit]
Description=Run PQCScan Audit Weekly

[Timer]
OnCalendar=Sun *-*-* 03:00:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target
EOF

反映する

sudo systemctl daemon-reload

sudo systemctl enable \
  --now \
  pqcscan-audit.timer

手動実行する

sudo systemctl start \
  pqcscan-audit.service

ログ確認

sudo journalctl \
  -u pqcscan-audit.service \
  -n 200 \
  --no-pager

次回実行日時

systemctl list-timers \
  pqcscan-audit.timer

最新レポートを社内Webサーバーで閲覧する

最新レポートを、アクセス制限された社内Webサーバーへ公開できます。

Nginxの例:

server {
    listen 192.168.1.10:8080;
    server_name _;

    root /var/lib/pqcscan/results/latest;

    location / {
        try_files $uri $uri/ /report.html;
    }
}

ただし、レポートには社内ホスト名、IPアドレス、ポート番号などが含まれます。

インターネットへ公開してはいけません。

次のいずれかで制限します。

  • 社内LANだけで待ち受ける
  • VPN経由だけで利用する
  • Basic認証
  • IPアドレス制限
  • SSO

CI/CDで公開テスト環境を検査する

Webアプリケーションのステージング環境について、PQC対応を継続的に確認できます。

GitHub Actionsから到達可能な公開テスト環境だけを対象にしてください。

社内サーバーを検査する場合は、ネットワーク設計を確認したself-hosted runnerを使用します。

name: PQC Readiness Check

on:
  workflow_dispatch:
  schedule:
    - cron: "30 18 * * 0"

permissions:
  contents: read

jobs:
  pqc-audit:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Checkout PQCScan
        uses: actions/checkout@v4
        with:
          repository: anvilsecure/pqcscan
          path: pqcscan
          ref: "0.8.0"

      - name: Build PQCScan
        working-directory: pqcscan
        run: cargo build --release --locked

      - name: Scan staging TLS
        run: |
          ./pqcscan/target/release/pqcscan \
            tls-scan \
            -T security/pqc-tls-targets.txt \
            --only-hybrid-algos \
            -o tls-results.json

      - name: Create HTML report
        run: |
          ./pqcscan/target/release/pqcscan \
            create-report \
            -i tls-results.json \
            -o pqc-report.html

      - name: Convert to CSV
        run: |
          python3 security/pqcscan_to_csv.py \
            tls-results.json \
            -o pqc-inventory.csv \
            --fail-on-unsupported

      - name: Upload reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: pqc-readiness-report
          path: |
            tls-results.json
            pqc-report.html
            pqc-inventory.csv

GitHubリポジトリのmainブランチではなく、検証済みのタグやコミットへ固定してください。


PQCScanの検査結果でよくあるパターン

パターン1:SSHがPQC対応

pqc_supported: true

pqc_algos:
- mlkem768x25519-sha256
- sntrup761x25519-sha512

鍵交換については、耐量子ハイブリッド方式を利用できる状態です。

実際のSSH接続で、その方式が選択されるかも確認します。

パターン2:SSHは従来方式のみ

pqc_supported: false

nonpqc_algos:
- curve25519-sha256
- diffie-hellman-group14-sha256

OpenSSHのバージョン、KexAlgorithms設定、OS更新計画を確認します。

パターン3:公開TLSはPQC対応、オリジンは非対応

public.example.com:
  X25519MLKEM768 supported

origin.example.internal:
  PQC not detected

利用者からCDNまでの通信は耐量子鍵交換に対応していても、CDNからオリジンまでは従来型の可能性があります。

完全な経路を分けて管理します。

パターン4:接続失敗

error:
Connection timed out

PQC非対応ではなく、監査端末から接続できなかった状態です。

ファイアウォール、アクセス制限、ポート番号を確認します。

パターン5:TLSサーバーはPQCに対応しているが証明書は従来型

PQCScanでは鍵交換がPQC対応として表示されます。

しかし、サーバー証明書がRSAやECDSAで署名されている可能性があります。

これは直ちに現在の通信内容が将来復号されるという意味ではありません。

鍵交換と署名は、別々に移行を管理する必要があります。


「PQC対応だから安全」と断定してはいけない理由

理由1:サーバーが対応しているだけかもしれない

対応アルゴリズムを提示していても、実際のクライアントが利用しなければ、接続は従来型になります。

理由2:CDNまでしか対応していない可能性がある

CDN、ロードバランサー、WAF、オリジンという複数区間を確認する必要があります。

理由3:署名方式は別問題

鍵交換がPQC対応でも、SSHホスト鍵、TLS証明書、コード署名が従来型の可能性があります。

理由4:保存データは対象外

ファイル暗号化、バックアップ、データベース暗号化は、PQCScanでは確認できません。

理由5:ダウングレードや設定変更が起こり得る

更新後に古い設定ファイルを戻したり、ロードバランサーを交換したりすると、PQC対応が失われる可能性があります。

定期検査と差分管理が必要です。


製造業で優先して確認すべき対象

製造業では、一般的なWebサーバーだけでなく、長期間利用する古い設備・管理システムが存在します。

次の対象を棚卸しします。

  • 基幹販売管理サーバー
  • 生産管理サーバー
  • 在庫管理サーバー
  • ファイルサーバー
  • バックアップサーバー
  • NAS
  • QNAP
  • 社内Gitサーバー
  • 監視サーバー
  • VPN装置
  • クラウドVPS
  • Web明細サーバー
  • 取引先向けポータル
  • 設備保守用PC
  • 長期間更新されていないLinux機器

ただし、PQCScanで直接確認できるのは、SSHまたはTLSサービスを提供している対象です。

VPN、独自プロトコル、Windows RDP、SMBなどは、別の調査方法が必要です。


実務向けのPQC移行台帳

PQCScanの結果に、資産情報と対応計画を追加します。

項目 内容
資産名 サーバー・サービス名
管理部署 情報システム・製造・営業など
ホスト FQDNまたはIP
プロトコル SSHまたはTLS
ポート 22、443など
PQC対応 対応・非対応・確認失敗
検出方式 ML-KEM、sntrupなど
情報機密度 公開・社内・機密・高度機密
データ保護期間 1年、5年、10年以上
OS・製品 Ubuntu、QNAP、CDNなど
更新期限 次回OS更新や更改時期
対応方針 更新・移行・廃止・例外
最終検査日 PQCScan実施日時

特に重要なのが、データ保護期間です。

10年以上秘密にする必要がある通信は、将来の復号リスクを早い段階で考慮する必要があります。


PQCScan導入後の推奨ロードマップ

第1段階:対象を集める

  • SSHサーバー一覧
  • HTTPSサーバー一覧
  • ロードバランサー一覧
  • CDN一覧
  • オリジンサーバー一覧
  • 廃止予定システム

第2段階:一括検査する

  • SSHとTLSを別々に検査
  • JSONを保存
  • HTMLレポートを作成
  • CSVへ変換

第3段階:優先度を付ける

  • 外部公開
  • 機密度
  • 保護期間
  • 更新困難度
  • 廃止予定

第4段階:更新する

  • OSアップグレード
  • OpenSSH更新
  • TLSライブラリ更新
  • CDN・ロードバランサー更新
  • 古い機器の置き換え

第5段階:再検査する

  • 更新前後を比較
  • 実際の接続方式を確認
  • 既存クライアントとの互換性を確認
  • レポートを保存

第6段階:定期監査する

  • 週次または月次検査
  • 設定後退の検出
  • 新規サーバーの確認
  • 廃止資産の削除

よくある質問

量子コンピュータはまだないのに、今対応する必要がありますか?

すべてのシステムを今すぐ置き換える必要はありません。

ただし、暗号資産の棚卸し、長期間秘密にすべき情報の特定、更新困難な機器の把握は、今から始める必要があります。

PQCScanでfalseなら、すぐ危険ですか?

現在の古典コンピュータに対し、直ちに通信が解読されるという意味ではありません。

将来の量子攻撃に対する鍵交換保護が確認できなかった、という意味です。

OpenSSH 9.0以上なら必ず大丈夫ですか?

設定でPQC方式を除外している場合や、ベンダー実装に差がある場合があります。

バージョン番号だけではなく、PQCScan、ssh -Q kexsshd -Tを組み合わせて確認してください。

PQCScanはログインを試みますか?

SSHのアルゴリズム交渉を確認するもので、通常のユーザー認証やログインは必要ありません。

TLS証明書のPQC対応も確認できますか?

主に鍵交換グループの対応確認です。証明書署名の耐量子対応は別途調査が必要です。

社外サーバーを自由に検査してよいですか?

自社管理または明確な許可を得た対象だけにしてください。

PQC対応を有効にすると古い端末が接続できなくなりますか?

ハイブリッド方式を既定候補へ追加するだけであれば、通常は共通する従来方式へフォールバックできます。

しかし、PQC方式だけに限定すると、古いクライアントが接続できなくなる可能性があります。

移行期間は互換性テストが必要です。


PQCScanの今後に期待する機能

現在のPQCScanは、目的をSSHとTLSのPQC鍵交換検査へ絞ったツールです。

今後、企業利用では次の機能も期待されます。

  • SARIF出力
  • CycloneDX CBOM出力
  • 前回結果との差分
  • 終了コードによる判定の標準化
  • タイムアウトのCLI設定
  • TLS証明書署名方式の検査
  • SSHホスト鍵署名方式の検査
  • Prometheusメトリクス
  • 脆弱性管理システムとの連携
  • 資産タグや所有部署の追加

ただし、機能を絞っているからこそ、導入しやすく、結果を理解しやすいという利点があります。


まとめ

PQCScanは、SSHとTLSサーバーが耐量子鍵交換に対応しているかを、実際のネットワーク接続によって確認するオープンソースツールです。

Rust製の単一実行ファイルとして利用でき、次の機能を備えています。

  • SSHサーバー検査
  • TLSサーバー検査
  • 複数対象の一括検査
  • JSON出力
  • HTMLレポート作成
  • ハイブリッド方式限定検査
  • 従来型アルゴリズムの追加検査
  • 並列検査

OpenSSHでは、すでに耐量子ハイブリッド鍵交換が標準的に利用されています。

  • OpenSSH 9.0:sntrup761とX25519のハイブリッド方式を標準利用
  • OpenSSH 9.9:ML-KEM-768とX25519のハイブリッド方式を追加
  • OpenSSH 10.0:mlkem768x25519-sha256を既定方式に変更
  • OpenSSH 10.1:非PQC接続への警告を追加

しかし、企業内には古いUbuntu、NAS、ロードバランサー、VPN装置、独自Webシステムなどが残っています。

重要なのは、すべてを直ちに交換することではありません。

まず次を把握することです。

  1. どのサーバーがSSH・TLSを提供しているか
  2. どの接続がPQC鍵交換に対応しているか
  3. どの通信で長期間秘密にすべき情報を扱うか
  4. どのシステムが更新困難か
  5. いつ更新・廃止するか

PQCScanの結果がtrueであっても、システム全体が完全に耐量子化されたわけではありません。

鍵交換、証明書署名、SSHホスト鍵、VPN、コード署名、保存データ暗号化は、それぞれ別に確認する必要があります。

それでも、SSHとTLSという重要な入口について、実際の対応状況を短時間で可視化できる価値は大きいでしょう。

耐量子暗号への移行で最初に必要なのは、新しい暗号方式を導入することではありません。現在の暗号利用状況を、推測ではなく実測によって把握することです。

PQCScanは、その第一歩を始めるための実用的なツールです。


参考資料

本記事は2026年7月20日時点の公式情報とPQCScan 0.8.0の仕様を基に作成しています。PQCScan、OpenSSH、TLS実装、PQC標準は今後更新される可能性があります。導入時は必ず公式リポジトリ、リリースノート、使用OSの提供元情報を確認してください。

ABOUT ME
Yuusuke
業務システムの要件定義・設計・実装を担当。Python、PHP、JavaScript、VBA、Oracle、MySQLなど、実務と個人開発で検証した内容を発信しています。