TLSでポスト量子暗号を試してみる (その2: 署名)

## 1.  はじめに

この記事では、製品組み込み向けTLSライブラリー[wolfSSL](https://www.wolfssl.jp)を使って、TLSでポスト量子暗号を実際に動かして試してみます。前回の「鍵交換」に引き続き今回は「署名」を中心にみていきいます。ポスト量子暗号標準化(Post-Quantum Cryptography Standardization)の背景については[「TLSでポスト量子暗号を試してみる (その１：鍵交換）」](https://qiita.com/kj1/items/8531feac2f3a56e6d6d9)を参照してください。

## 2. ポスト量子暗号の署名アルゴリズム

米NISTでは、署名アルゴリズムとして現在(2024/8)、格子ベースと階層ハッシュベースの二つのアルゴリズムが標準化されています。


|分類|NIST標準名|FIPS|開発名|強度|
|---|---|---|---|---|
|格子ベース|ML-DSA|FIPS-204|CRYSTALS-Dilithium|ML-DSA-44<br>ML-DSA-65<br>ML-DSA-87|
|ハッシュ<br>ベース|FIPS-205|SLH-DSA|SPHINCS+||


表1：　NISTで標準化されたPQCの署名アルゴリズム



署名のためのAPIとしてはwolfSSLでは、鍵ペアの生成、署名、検証などのAPIが用意されていて、概ねこれまでの公開鍵署名と同じようなイメージでプログラムすることができるようになっています。

|API名|機能|
|---|---|
|wc_dilithium_init|初期化|
|wc_dilithium_make_key|鍵ペアの生成|
|wc_dilithium_sign_msg|署名|
|wc_dilithium_verify_msg|検証|

表2：主な署名用API

## 3. TLS1.3の署名と検証

下の図はTLSの公開鍵証明書を利用した公開鍵署名と検証の関連を示しています。ポスト量子暗号でもこの枠組みはそのまま踏襲します。

サーバ側はサーバ証明書にCAによる署名をもらいます。クライアント側はCA証明書を用意しておきます。TLSハンドシェーク時には、TLS1.3では"Certificate"メッセージでサーバ証明書を送り、"Certificate Verify" でそれまでのハンドシェークメッセージに対する署名を送ります。クライアント側では、受けとったサーバ証明書が真正であることをCA証明書の公開鍵で検証し、サーバ証明書の公開鍵で送られてきた署名を検証します。

![ServerVerification.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263939/8ff1b2a5-f282-c38d-ce81-751765f90803.png)


　　　　　　　　　　　　　　　　　　　　　　　　図1: TLSの署名と検証

これらの全ての署名、検証をポスト量子アルゴリズムで実現することで、量子コンピューティングの時代において通信相手に対するピア認証が安全にできることになります。

## 4. サンプルプログラムを動かしてみる

### 4.1 サンプル証明書等のダウンロード

まずは、ポスト量子暗号を使ったX.509証明書や鍵のサンプルをwolfSSLのGithubレポジトリからダウンロードします。

```
$ git clone https://github.com/wolfssl/osp/oqs
$ cd osp/oqs
```

### 4.2 wolfSSLとサンプルのビルド

最新のwolfSSLライブラリーはポスト量子暗号に対応しているので、TLSによる通信自身については普通のTLSサーバとクライアントを使用してもよいのですがTLS1.3ではハンドシェークメッセージが暗号化されていてそのままでは見えません。これを復号してメッセージを見るためにはサンプルプログラム中にセッション鍵情報を取得する仕掛けを埋め込んでおいて、その情報をWiresharkで参照する必要があります。wolfSSLライブラリのほうもそのためのオプションを指定してビルドする必要があります。

wolfSSLのダウンロード、ビルド手順は ["その１：鍵交換"](https://qiita.com/kj1/items/8531feac2f3a56e6d6d9)で紹介した手順と基本的に同じですが、上記オプション指定を追加します。

```
$ git clone https://github.com/wolfssl/wolfssl
$ cd wolfssl
$ ./autogen.sh
$ ./configure --enable-kyber --enable-dilithium CFLAGS="-DHAVE_SECRET_CALLBACK"
$ make all
$ sudo make install
```
次にPQCのサーバ、クライアントのサンプルプログラムをダウンロード、ビルドします。

```
$ git clone https://github.com/wolfssl/wolfssl-examples
$ cd wolfssl-examples/pq/tls
$ make CFLAGS=-DHAVE_SECRET_CALLBACK
```

### 4.3 TLSハンドシェークを見てみる

ビルドができたら、サンプルサーバとクライアントを動作させてTLSハンドシェークを見てみることにします。サンプルはwolfssl-examples/pq/server-pq-tls13.cとclient-pq-tls13.cを使います。

サンプルのサーバ、クライアントは次のようにオプションアーギュメントで証明書、鍵ファイル名を指定できるようになっています。

```
$ ./server-pq-tls13 [<サーバ証明書> <プライベート鍵>]
$ ./client-pq-tls13 IPアドレス [<CA証明書>] 
```

Wireshark を立ち上げて、まず、サンプルサーバを起動します。

```
$ ./server-pq-tls13 <サンプル証明書ディレクトリ>/mldsa65_entity_cert.pem  <サンプル証明書ディレクトリ>/mldsa65_entity_key.pem 
```
次にクライアント側を起動し、サーバに送るメッセージを入力します。

```
$ ./client-pq-tls13 127.0.0.1 <サンプル証明書ディレクトリ>/mldsa65_root_cert.pem 
Message for server: <適当なメッセージを入力>
Server: I hear ya fa shizzle!
```
その内容はサーバ側に表示されます。

```
Waiting for a connection...
Client connected successfully
Client: <クライアント側で入力したメッセージ>

Waiting for a connection...
```

この状態でWireshakeを "Filter:tls" で見てみると、下のように"Server Hello" が暗号化された状態で表示されます。

```
No.	Time	Source	Destination	Protocol	Length	Info
No.	Time	Source	Port	Destination	Port	Protocol	Length	Info
95	2025/058 02:33:25.833379	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	1952	Client Hello
7	2025/058 02:33:25.843167	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	1820	Server Hello
9	2025/058 02:33:25.843938	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	84	Application Data
11	2025/058 02:33:25.844119	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	6081	Application Data
13	2025/058 02:33:25.844580	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	3395	Application Data
15	2025/058 02:33:25.844739	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	130	Application Data
17	2025/058 02:33:25.848504	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	130	Application Data
21	2025/058 02:33:36.006824	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	91	Application Data
23	2025/058 02:33:36.007041	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	100	Application Data
25	2025/058 02:33:36.007083	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	80	Application Data
```

この時、サンプルプログラムを起動したディレクトリの直下に "sslkeylog.log"　という名前でセッション鍵情報のファイルが生成されているはずです。Wirehsarkの設定→プロトコル→TLS→"(Pre)-Master-Secret log file name"でこのファイル名を指定すると下のように各メッセージが復号されて表示され普通のTLS1.3のハンドシェークであることがわかります。

```
No.	Time	Source	Destination	Protocol	Length	Info
5	2025/058 02:33:25.833379	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	1952	Client Hello
7	2025/058 02:33:25.843167	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	1820	Server Hello
9	2025/058 02:33:25.843938	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	84	  Encrypted Extensions
11	2025/058 02:33:25.844119	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	6081	Certificate
13	2025/058 02:33:25.844580	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	3395	Certificate Verify
15	2025/058 02:33:25.844739	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	130	Finished
17	2025/058 02:33:25.848504	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	130	Finished
21	2025/058 02:33:36.006824	127.0.0.1	59212	127.0.0.1	11111	TLSv1.3	91	Application Data
23	2025/058 02:33:36.007041	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	100	Application Data
25	2025/058 02:33:36.007083	127.0.0.1	11111	127.0.0.1	59212	TLSv1.3	80	Alert (Level: Warning, Description: Close Notify)
```

まずは、 "Client Hello" の内容をみてみます。"Signature Algorithms" の "Signature Hash Algorithms" を見てみると "Unknown Unknown (0x0904, 0905, 0906)" のように生のID値が示されてりるものが三つほどあるのがわかります。wolfSSLのこのバージョンでサポートしているML-DSAなのですが、通常版のWiresharkではまだこれらのアルゴリズムのシンボル表示に対応していないのでこのように表示されます。


```
        Handshake Protocol: Client Hello
            ...
            Extension: signature_algorithms (len=34)
                Type: signature_algorithms (13)
                Length: 34
                Signature Hash Algorithms Length: 32
                Signature Hash Algorithms (16 algorithms)
                    Signature Algorithm: ecdsa_secp521r1_sha512 (0x0603)
                    ...
                    Signature Algorithm: Unknown Unknown (0x0904) <--- ML-DSA44
                    Signature Algorithm: Unknown Unknown (0x0905) <--- ML-DSA65
                    Signature Algorithm: Unknown Unknown (0x0904) <--- ML-DSA87
                    ...
```

次に、"Certificate" の内容を見てみると、subjectPublicKey, SignatureにはAlgorithm IDとして生のIDと値が表示されています。


```
            Handshake Type: Certificate (11)
            Length: 5999
            Certificate Request Context Length: 0
            Certificates Length: 3785
            Certificates (3785 bytes)
                Certificate Length: 3780
                Certificate: 30820ec0308209baa00302010202020401300706052bce0f0304308196310b3009060355… 
                    signedCertificate
                        version: v3 (2)
                        serialNumber: 0x0401
                        signature (iso.3.9999.3.4)
                            Algorithm Id: 1.3.9999.3.4 (iso.3.9999.3.4)
                        ...
                    subjectPublicKeyInfo
                        algorithm (sigAlgs.18)
                        Algorithm Id: 2.16.840.1.101.3.4.3.18 (sigAlgs.18)
                        subjectPublicKey […]:8ccbc09958fe77551c7518a22f5ece343862ba700ec4ee                        
                        ...
                    algorithmIdentifier (sigAlgs.18)
                        Algorithm Id: 2.16.840.1.101.3.4.3.18 (sigAlgs.18)
                    encrypted […]: 050d9bcd297ead848d8e63745962b22a74b9495116ee6ca3aab21fea96547d0...


```



## 5. まとめ

この記事では、ポスト量子暗号によるTLSプロトコルのうち署名部分ついてみてきました。記事中にもコメントしたように、従来のRSAやECCによる署名にくらべて署名サイズはかなり大きなものになってしまう点でアルゴリズムにはまだまだ改善の余地があります。しかし、現時点でTLSの枠組みののなかで実際に使用できるアルゴリズムができあがりつつあることが感じられるかと思います。