本文へスキップ
ecdsa.com

図解

図解:ポスト量子 TLS 1.3 接続

これは、実際の通信からキャプチャした 1 つの TLS 1.3 ハンドシェイクを、1 バイトずつ分解したものです。両者は、ポスト量子の ML-KEM-768 と従来の X25519 を組み合わせたハイブリッド方式 X25519MLKEM768 で鍵を合意します。そのため、今日の通信を記録しておき、後で楕円曲線を破る攻撃者に対しても、セッションの機密性は保たれます。以下の数値はすべて、キャプチャしたバイト列から再計算し、OpenSSL がログに記録した値と照合したものです。説明用の架空の値は一切ありません。

このセッションの使い捨ての鍵

ハンドシェイクは、そのシークレットがわかっている場合にのみ再現できます。そのため、このデモではシークレットを公開しています。ここにある値はすべて、このセッション限りの使い捨てのシークレットで、キャプチャ用に生成され、二度と使われることはありません。公開しても、他の接続については何も明らかになりません。これらの値があるからこそ、このページも読者自身も、以下のすべての手順を再計算できます。

ML-KEM 鍵生成のシード d ‖ z(64 バイト)
84745c7adf177dfb94e15c7cdef6f8006897b295bdafaadd090ef050c004b212921eb10b6b41e8f0221e0f7f164aadef44f4cba1139edd522c9f0860db6b0cde
クライアントの X25519 秘密鍵(ハイブリッドのキーシェア)
784edfa4400a57540a7cbd71957c8ab182ae801a5c519755abe69f553b9e8c5d
クライアントの X25519 秘密鍵(単独のキーシェア)
d81df8305a5f24f9077b3d669885d0d7b93ec55fe405ccde814bac62e778f656
ML-KEM のカプセル化に使う乱数 m(32 バイト)
4888d955ca1924fb19e233b6d516249f84e945e1d8345e9816b7fbaa2c3632b7
サーバーの X25519 秘密鍵
c81c64cbae9dc5549313f148f09f00da5f8fde45be679697e02204a781796145
サーバー証明書の秘密スカラー(P-256)
11aa8fac7486c821d6c57baa03c3a3e119a635f33aaa2043bba12d7f20a40a99

ハイブリッドのキーシェアの構造

ポスト量子への移行にかかるコストは、すべて 2 つのフィールドに集約されます。X25519MLKEM768 は ML-KEM-768 の値と X25519 の値を連結したもので、このグループでは ML-KEM が先頭です(RFC 10024 §4)。クライアントは 1 つのキーシェア(key share)で両方を提示し、サーバーも両方で応答します。共有秘密も、32 バイトずつの 2 つの部分が同じ方法で連結されます。

クライアントのキーシェア:1216 バイト

ML-KEM-768 · 1184 バイト
X25519 · 32 バイト

サーバーのキーシェア:1120 バイト

ML-KEM-768 · 1088 バイト
X25519 · 32 バイト

共有秘密:64 バイト

ML-KEM-768 · 32 バイト
X25519 · 32 バイト

ポスト量子暗号の通信上のコスト

同じクライアントとサーバーで、他の条件はすべて同じまま、X25519 だけを使った別のハンドシェイクも行いました。ハイブリッドの鍵素材は、どちらの方向でも 1 キロバイト以上大きくなります。これが唯一の実質的なコストで、ClientHello が 1 つのパケットに収まらなくなります。

測定項目X25519MLKEM768X25519差
ClientHello(レコード全体)1,483 バイト261 バイト+1,222 バイト
key_share 拡張(クライアント)1,262 バイト42 バイト+1,220 バイト
supported_groups 拡張22 バイト20 バイト+2 バイト
ServerHello(レコード全体)1,215 バイト127 バイト+1,088 バイト
key_share 拡張(サーバー)1,128 バイト40 バイト+1,088 バイト
クライアントが送信したバイト数1,620 バイト398 バイト+1,222 バイト
サーバーが送信したバイト数2,326 バイト1,236 バイト+1,090 バイト
ClientHello の TCP セグメント数2 パケット1 パケット+1 パケット
クライアント → サーバーレコード 1 · 通信上のサイズ 1,483 バイト

ClientHello

クライアントが接続を開始します。TLS 1.3 を提案し、AES/ChaCha の暗号スイートを 3 つ提示し、対応する鍵交換グループを X25519MLKEM768 を先頭にして列挙します。さらに、優先する 2 つのグループそれぞれのキーシェアを送ります。その中には 1216 バイトのハイブリッドのキーシェアも含まれます。このメッセージは平文で送られ、この時点ではまだ何も秘密ではありません。

通信上のバイト列 · 1,483 バイト

  • この 1,478 バイトは、OpenSSL が -msg を付けてこのレコードについて出力した内容と一致します。
計算方法クライアントのキーシェア

クライアントは、最初のメッセージを送る前に、提示する一時鍵を生成します。ハイブリッドのグループでは ML-KEM-768 の鍵ペアと X25519 の鍵ペアを生成し、それぞれの公開鍵を連結して 1216 バイトの 1 つのキーシェアにします。単独の X25519 グループには、これとは別の鍵を用意します。

定義
d, z     ←$ B^32                                   FIPS 203 §7.1
(ek, dk) = ML-KEM-768.KeyGen_internal(d, z)        FIPS 203 §6.1
x_c      ←$ B^32,   X_c = X25519(x_c, 9)           RFC 7748 §5
share    = ek ‖ X_c                                1184 + 32 = 1216   RFC 10024 §4.1

y_c      ←$ B^32,   Y_c = X25519(y_c, 9)           key_share: x25519 (0x001d)
このセッションの値
d(32 バイト):秘密ベクトルに展開される値
84745c7adf177dfb94e15c7cdef6f8006897b295bdafaadd090ef050c004b212
z(32 バイト):暗黙的拒否に使う秘密の値
921eb10b6b41e8f0221e0f7f164aadef44f4cba1139edd522c9f0860db6b0cde
1184 バイトのカプセル化鍵の SHA-256
4a2903bee32a7a18d3294d93a4d24d6ecc8c07bc098bab31899689f87a572be8
クライアントの X25519 秘密鍵(ハイブリッドのキーシェア)
784edfa4400a57540a7cbd71957c8ab182ae801a5c519755abe69f553b9e8c5d
クライアントの X25519 公開鍵(ハイブリッドのキーシェア)
09e0802926363ed192b02f5261520f2056ba7af9a0acd1eee5bbe50181e0cf29
クライアントの X25519 秘密鍵(単独のキーシェア)
d81df8305a5f24f9077b3d669885d0d7b93ec55fe405ccde814bac62e778f656
クライアントの X25519 公開鍵(単独のキーシェア)
67aab4deeec206fa89034014d772bdd47cedd7930610e81f634871565424f14d
これで証明されること
  • シードから ML-KEM-768 の鍵を生成すると、クライアントのキーシェアにあるカプセル化鍵が再現されます。
  • カプセル化鍵の 768 個の係数はすべて q = 3329 未満(最大値 3326)のため、サーバーはこの鍵を受け入れます(FIPS 203 §7.2)。
  • X25519 の秘密鍵から、クライアントのハイブリッドのキーシェアにある公開値が再現されます。
  • 単独の X25519 のキーシェアは、ハイブリッドのキーシェアとは異なる独自の鍵を使っています。
自分で再現する
printf '%s' 3054020100300b06096086480165030404020442804084745c7adf177dfb94e15c7cdef6f8006897b295bdafaadd090ef050c004b212921eb10b6b41e8f0221e0f7f164aadef44f4cba1139edd522c9f0860db6b0cde | unhex > client-mlkem.der
openssl pkey -inform DER -in client-mlkem.der -pubout -outform DER | tail -c 1184 | openssl dgst -sha256 -binary | tohex

出力 4a2903bee32a7a18d3294d93a4d24d6ecc8c07bc098bab31899689f87a572be8

printf '%s' 302e020100300506032b656e04220420784edfa4400a57540a7cbd71957c8ab182ae801a5c519755abe69f553b9e8c5d | unhex > x25519.der
openssl pkey -inform DER -in x25519.der -pubout -outform DER | tail -c 32 | tohex

出力 09e0802926363ed192b02f5261520f2056ba7af9a0acd1eee5bbe50181e0cf29

サーバー → クライアントレコード 2 · 通信上のサイズ 1,215 バイト

ServerHello

サーバーは TLS 1.3、暗号スイート TLS_AES_128_GCM_SHA256、グループ X25519MLKEM768 を選び、自身の 1120 バイトのハイブリッドのキーシェアを返します。これが平文で送られる最後のメッセージです。ここから両者はハンドシェイク鍵を計算でき、以降のメッセージはすべて暗号化されます。

通信上のバイト列 · 1,215 バイト

  • この 1,210 バイトは、OpenSSL が -msg を付けてこのレコードについて出力した内容と一致します。
  • OpenSSL の s_client は、ネゴシエートされたグループとして X25519MLKEM768 を出力しました。
  • OpenSSL の s_client は、暗号として TLS_AES_128_GCM_SHA256 を出力しました。
計算方法サーバーのキーシェア

サーバーには、掛け合わせるための対応する秘密鍵がありません。ML-KEM は KEM であり、Diffie-Hellman ではないからです。サーバーはクライアントの ML-KEM 鍵に対して新しい秘密をカプセル化し、暗号文とポスト量子の共有秘密を生成します。さらに、自身の X25519 鍵を生成します。サーバーのキーシェアは、暗号文のあとに X25519 公開値を続けたものです。

定義
m      ←$ B^32
(K, c) = ML-KEM-768.Encaps_internal(ek, m)         FIPS 203 §6.2
x_s    ←$ B^32,   X_s = X25519(x_s, 9)             RFC 7748 §5
share  = c ‖ X_s                                   1088 + 32 = 1120   RFC 10024 §4.2
このセッションの値
ML-KEM のカプセル化に使う乱数 m(32 バイト)
4888d955ca1924fb19e233b6d516249f84e945e1d8345e9816b7fbaa2c3632b7
ML-KEM の共有秘密 K(32 バイト)
dad26963f7510c64b7c7d9b80731d6b573abf15999f5bed3076173e3f8c0cbdb
1088 バイトの ML-KEM 暗号文の SHA-256
30d5f4e26e8548b8796088ef0d56d307cab52802ab03e3cea966cb1391dbfc2a
サーバーの X25519 秘密鍵
c81c64cbae9dc5549313f148f09f00da5f8fde45be679697e02204a781796145
サーバーの X25519 公開鍵
339fdc7e19b200cb062df86bdf0e022c949abacd7db8dbdae0a92a6d9ba8d530
これで証明されること
  • 記録された乱数を使ってクライアントの鍵に対してカプセル化すると、サーバーの暗号文が再現されます。
  • サーバーの X25519 秘密鍵から、そのキーシェアにある公開値が再現されます。
計算方法共有秘密

これで両者は同じ 64 バイトを得られます。クライアントは ML-KEM の秘密鍵で暗号文をデカプセル化し、両者が X25519 の交換を完了させ、32 バイトずつの 2 つの結果を ML-KEM を先にして連結します。攻撃者がこれを復元するには両方の半分を破らなければなりません。これこそがハイブリッド方式の目的です。

定義
K  = ML-KEM-768.Decaps(dk, c)                      FIPS 203 §7.3
Z  = X25519(x_c, X_s) = X25519(x_s, X_c)           RFC 7748 §6.1
ss = K ‖ Z                                         32 + 32 = 64   RFC 10024 §4.3
このセッションの値
ML-KEM の共有秘密 K(32 バイト)
dad26963f7510c64b7c7d9b80731d6b573abf15999f5bed3076173e3f8c0cbdb
X25519 の共有秘密 Z(32 バイト)
568c3055b3ac3516f0d780bbe022beb336ade7e7bae81752f2de6df09ebdc428
共有秘密 = K ‖ Z(64 バイト)
dad26963f7510c64b7c7d9b80731d6b573abf15999f5bed3076173e3f8c0cbdb568c3055b3ac3516f0d780bbe022beb336ade7e7bae81752f2de6df09ebdc428
これで証明されること
  • クライアントのデカプセル化とサーバーのカプセル化から、同じ ML-KEM の共有秘密が得られます。
  • 両者が同じ X25519 の共有秘密を計算します。
  • RFC 9846 §7.4.2 の要件どおり、X25519 の共有秘密はすべてゼロではありません。
  • 共有秘密の先頭 32 バイトは ML-KEM の共有秘密で、RFC 10024 §4.3 が定める順序のとおりです。
自分で再現する
printf '%s' 3054020100300b06096086480165030404020442804084745c7adf177dfb94e15c7cdef6f8006897b295bdafaadd090ef050c004b212921eb10b6b41e8f0221e0f7f164aadef44f4cba1139edd522c9f0860db6b0cde | unhex > client-mlkem.der
unhex > ciphertext.bin <<'EOF'
5db0e57cb763af0d1678753192830257df14de0eb4dd0d9a2d7ee2a7c94f4aab
2526fd0957b0c1025efff1763488c3bff22007fdd19b33a0e5605fc2b50ed3aa
adee4643eaa746cc65882d54a49ce4cb208226f6c732dc39981d81de9b788740
f6361120dc130f814bf5dd41d4df8682468e8ec8e2b3d5056cf245df0e0febe7
cf7423f725533dc39210093d57134c0e86a716d6d62694c9548082a5cd9352f2
fb8254397df2921d3a48702df9f622a7a539ba6110bec4736b6d15e38c558105
d6954e5fe1aec5b5ae065222fe1df4b1639e001695dcfba2d0aa09d14f9fee31
201383e58a16709631cb7b182ffbae8dc4e1345afd1763e55bb0714e92470c53
805f05ace3ce45402563cfd08db5429ffe5eeb033a7695530ee01ab7c5a179b1
1e3cd17082f457a158861fda8a9c08e7987dc5e18a48955ddba38766e14e09c3
b6319dbb5cb7c6c8e4aad9d40c2ec2b69e8ab793f742765d2af2644afc87cc1f
add8e0dc76f9725dfb9063f116ef8b02c7dc949160e22d716cfe1bed9a7eccae
e26ebc9615fd2c6e53a93bf97db63bbcae24bff5082a83dd2128fec9297cf449
2811d09363a21e98ac4869512ede9300c3fe5f049d7348404c56c37e1329fbbe
90fa854ef8b4de86c67bd16c4263f5eb99a50939a80086c9a1c0d038343d8c9e
d8b9300a6556d950492925628fb281cd077c82681f75e7aae144617adf613f49
11ff6e8f4246039f2ace861f2b2ec1d0f555e3e1a7fce9c80c6c2a8a062d0a64
7be31fc4b42c8776cd4b6517c4aa939a398b110c052e30a8eb746c476ee0dfc8
8866fbd8fdc816d20add8dbfeabcac6e0dac0f41a4e4822ab1f51b54e4bc178f
4eb7c07670704e425fd99205b8efd2f3fbccb200760127b1188fab5d7be84b06
87d779bfbe63e3b1db13f34ab9205d9f06312046e77737a05c070f1b808b5c80
9aa9e9945c52e4bf46d5b328030b42dbfba641cc0a39e6ace09361371d25fb3e
e78507a6fadfc2b6cd4a06851488b001d8b40a1ceb647b7d9debcbff6c375a13
73540d114e9bca1377b83192209bdb70824784b23da425bc7c8cca3ba0845d14
45e732d4e2327348f3a1d1f2499753bd5786ef80ef9a47c7293eedeef64d21ef
4ec47e847303fb963deff2f873efc8f2cfef263bdc40c34cb9aa80584e1ffd03
efe811cd6e12e4e6cdbda8a1ddc2df5f8f86462311425bae494f087f28524721
43e8bf6f53fb68f6063961b07dd34b0a4ebb4aba6b0f989f5955c4fe10088cae
6f7531a6b699f9e9dd34b2fc3ae102409bc97775b7bdc1e0028042e7c27fb8ed
c43c611b3ffd91de6a08a44700dd9b2d241488710e7cfabf974db0fd8a8f9b68
fee885b69d2cac23f6531af4f69c6f58e0d1ce620c47578c0bf4d3f3435df6ba
67556efaa65ec7bb518c76e840747c8454d0538f36134ee8beb0bd24a616a42d
fc826926ff9608def9b1c433d6e9ad8fcf1e11543a9595795f69c6630b60f581
156d98ce7e53124ceb06ce7e6e8054c6849aa2039b96cde7f9eac19b036b44a7
EOF
openssl pkeyutl -decap -inkey client-mlkem.der -keyform DER -in ciphertext.bin -secret mlkem-secret.bin
tohex < mlkem-secret.bin

出力 dad26963f7510c64b7c7d9b80731d6b573abf15999f5bed3076173e3f8c0cbdb

printf '%s' 302e020100300506032b656e04220420784edfa4400a57540a7cbd71957c8ab182ae801a5c519755abe69f553b9e8c5d | unhex > own.der
printf '%s' 302a300506032b656e032100339fdc7e19b200cb062df86bdf0e022c949abacd7db8dbdae0a92a6d9ba8d530 | unhex > peer.der
openssl pkeyutl -derive -inkey own.der -keyform DER -peerkey peer.der -peerform DER | tohex

出力 568c3055b3ac3516f0d780bbe022beb336ade7e7bae81752f2de6df09ebdc428

計算方法ハンドシェイクトラフィックシークレット

共有秘密は TLS 1.3 の鍵スケジュール(RFC 9846 §7.1)に入力されます。鍵スケジュールは HKDF-Extract と Derive-Secret の手順を連ねたものです。事前共有鍵がない場合、アーリーシークレットはすべてゼロの値に対する HKDF-Extract です。そこに共有秘密を混ぜるとハンドシェイクシークレットが得られ、そこから ClientHello…ServerHello のトランスクリプトに基づいて、クライアントとサーバーのハンドシェイクトラフィックシークレットが導出されます。

定義
early_secret     = HKDF-Extract(salt = 0, IKM = 0)            0 = 00 × 32
derived          = Derive-Secret(early_secret, "derived", "")
handshake_secret = HKDF-Extract(salt = derived, IKM = K ‖ Z)
c_hs_traffic     = Derive-Secret(handshake_secret, "c hs traffic", ClientHello ‖ ServerHello)
s_hs_traffic     = Derive-Secret(handshake_secret, "s hs traffic", ClientHello ‖ ServerHello)

Derive-Secret(S, label, M)        = HKDF-Expand-Label(S, label, SHA-256(M), 32)
HKDF-Expand-Label(S, label, C, n) = HKDF-Expand(S, HkdfLabel, n)
HkdfLabel = uint16(n) ‖ uint8(6 + |label|) ‖ "tls13 " ‖ label ‖ uint8(|C|) ‖ C
このセッションの値
early_secret = HKDF-Extract(0, 0)
33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a
SHA-256(""):空のコンテキストのハッシュ値
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
導出シークレット
6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
handshake_secret
06b849eff15c46a4401e154110a2e63a14f7333237260156f7f1b67ca3305198
SHA-256(ClientHello ‖ ServerHello)
0da9cb1015db6852c07fbdc2389ad83ee8b1898a147932620753ce82d3c0709c
「c hs traffic」の HkdfLabel
002012746c73313320632068732074726166666963200da9cb1015db6852c07fbdc2389ad83ee8b1898a147932620753ce82d3c0709c
client_handshake_traffic_secret
8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623
server_handshake_traffic_secret
46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47f
これで証明されること
  • 両方の SSLKEYLOGFILE の CLIENT_HANDSHAKE_TRAFFIC_SECRET 行と一致します。
  • 両方の SSLKEYLOGFILE の SERVER_HANDSHAKE_TRAFFIC_SECRET 行と一致します。
自分で再現する
openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXTRACT_ONLY \
  -kdfopt hexsalt:0000000000000000000000000000000000000000000000000000000000000000 \
  -kdfopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 \
  HKDF | tohex

出力 33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a

openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
  -kdfopt hexkey:33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a \
  -kdfopt 'prefix:tls13 ' -kdfopt 'label:derived' \
  -kdfopt hexdata:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 \
  TLS13-KDF | tohex

出力 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba

openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXTRACT_ONLY \
  -kdfopt hexsalt:6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba \
  -kdfopt hexkey:dad26963f7510c64b7c7d9b80731d6b573abf15999f5bed3076173e3f8c0cbdb568c3055b3ac3516f0d780bbe022beb336ade7e7bae81752f2de6df09ebdc428 \
  HKDF | tohex

出力 06b849eff15c46a4401e154110a2e63a14f7333237260156f7f1b67ca3305198

unhex <<'EOF' | openssl dgst -sha256 -binary | tohex
010005c2030398fd30aaaf1617a1d20cc42c00c775d798dbaba001b1ae95ca6b
039f1e1fbbd7202bfbccd3b1cde003314afdea3e30508dd784674df104782760
5aeaca811f310d00061302130313010100057300000013001100000e70712d74
6c732e6578616d706c65000b000403000102000a0012001011ec001d0017001e
0018001901000101002300000016000000170000000d002a0028090509060904
04030503060308070808081a081b081c0809080a080b08040805080604010501
0601002b0003020304002d00020101003304ea04e811ec04c064180ac1469cd6
5bc990ebc8e4b7a5bf643e3a709d3f543a187c20c7aa082a02aecd74cb814452
7f8606b7a846953ca9f657ae6a7c7463f22ae0e44589139450d20b0311c7ca93
4a8898abde41c22c20b0367875bb8191d086a6a4141a6e4630d2588642d040c2
a73f8c82c8d6776a196c39f9066e9807a7b62b7745c12b6b04c2c2a0b235f889
8e16a1db48bdea02b66744b2e844b78a98a6796a10d2ab03143943f7d32286fc
8e78047854d555956c7db02079cbac0289cc60e345ceac998f7e4157b646c870
72627627076649b7b0ac7cc60294abe1229b42c4e8f9041d4b76268234550079
81ea36d3f62fc52382eec77f3796513b91a9a56ab1675466486475ebc287ac08
91185b37d1b36b31735a5d647b02481f0c1937dab04e3c956e15e69f89e83fb8
db43a428ab9d97a41e077e91a7b6ba26a350255c20b7a9791283aa5a181b8560
1b7971d993bcb698cd4dab1a92e8657b4329fad22705876904e2bd6ef5469cf8
a9c5674178d2b94d9065491b8fa530018bf15e0a1a17fefc67be14b07a755a80
5b650319025fc72c39b93141051ada437d154712647624b893be82f48ed0632d
a6317df11c56c14835b6d42b5a405f7eca9770675710b545e4f21cec18b139d7
b91a578a0416641f291950e5493cd170f7c8a4273a13bef81215bba72dc214d8
958db49a2004487aef97a672454758dc82f78704762c5264d596d9d38ce4589d
0f549710383924b16365596e2d6100cbc54e2eec665c572d13508b693c61445b
a7ea4b9a6a053c0c89ad955b43e8c035cb7130f6e85063d20e6f8652f592c069
02bedd0209ea14c79f9a902ee62a5a0b5f295779f1946a49b782bac2781b4922
354ca674a99b51d999e784659890acb2569dcc34aadcc51e52a637fea85dfc02
214d796fc64b424034cfa1e40cb114733e0c66fd4c990174c4dfe02d92e8b002
cc75d7597fb6ab5c27824c738c2bb3a239e2726ed9bc8cdecc944c992c9d3044
05493ff537a8d04632b3d3c0dca8ab98428cdd9184455455e7294d772c126e08
aa925c92c2660985b811ca041b7997646d909fe1671c83cccfbf2c49c7086587
d6beeb65a9596406bc95a88d7aa09ec58cc6c1b37366cd4d7c9ed49c90903995
f098412f1abfe1516ab63866b1a86d5f939691bb4dcff85c0cd62ada4c553c10
129e33b14f45a9794990f1501710c485b4c19370e1ca7d4326b092b12280316b
c76267957d5f48210a5baccb81a5d1fc985a77c8dc156fef1188d83616104623
1fe9352a16c8f2262946f3ca9015cb27e26e07052d19d7565e9623e1182cc2f0
67854708c921bd9f52852b810505b577146b31a2acb909440cfb2730e0ea520c
828b2b30c1d3c115700504a518ab05bac15c4a6fd634977748c6b0283b01784f
23179277199d1f399d66d5c2a3636d905a05b625a8e69686b43b166f635510e4
2dba6996bf10bdb0f2afea9ca9e9e8b1c41b012ed735d7646998d051f7124989
c29f3da18334ec22b5a47eea9c13fa9ac1d4e7bf9c1899abd96831f323bcd6ab
2d4caf33e86afeb475f7db256fd56da6c1367ce33a91260443e22573221f4295
3a4517046a2a951b9b41afdca2d156b5cb2c876ab032102392b02d7f437802dd
17ef543d6612ef4856f5558c2dddea2dde270fdab179bdb25309e0802926363e
d192b02f5261520f2056ba7af9a0acd1eee5bbe50181e0cf29001d002067aab4
deeec206fa89034014d772bdd47cedd7930610e81f634871565424f14d001b00
050400010003020004b60303498497776391725d59356de785c94cedb9c5def8
704f1426dfa548c1b589437f202bfbccd3b1cde003314afdea3e30508dd78467
4df1047827605aeaca811f310d130100046e002b000203040033046411ec0460
5db0e57cb763af0d1678753192830257df14de0eb4dd0d9a2d7ee2a7c94f4aab
2526fd0957b0c1025efff1763488c3bff22007fdd19b33a0e5605fc2b50ed3aa
adee4643eaa746cc65882d54a49ce4cb208226f6c732dc39981d81de9b788740
f6361120dc130f814bf5dd41d4df8682468e8ec8e2b3d5056cf245df0e0febe7
cf7423f725533dc39210093d57134c0e86a716d6d62694c9548082a5cd9352f2
fb8254397df2921d3a48702df9f622a7a539ba6110bec4736b6d15e38c558105
d6954e5fe1aec5b5ae065222fe1df4b1639e001695dcfba2d0aa09d14f9fee31
201383e58a16709631cb7b182ffbae8dc4e1345afd1763e55bb0714e92470c53
805f05ace3ce45402563cfd08db5429ffe5eeb033a7695530ee01ab7c5a179b1
1e3cd17082f457a158861fda8a9c08e7987dc5e18a48955ddba38766e14e09c3
b6319dbb5cb7c6c8e4aad9d40c2ec2b69e8ab793f742765d2af2644afc87cc1f
add8e0dc76f9725dfb9063f116ef8b02c7dc949160e22d716cfe1bed9a7eccae
e26ebc9615fd2c6e53a93bf97db63bbcae24bff5082a83dd2128fec9297cf449
2811d09363a21e98ac4869512ede9300c3fe5f049d7348404c56c37e1329fbbe
90fa854ef8b4de86c67bd16c4263f5eb99a50939a80086c9a1c0d038343d8c9e
d8b9300a6556d950492925628fb281cd077c82681f75e7aae144617adf613f49
11ff6e8f4246039f2ace861f2b2ec1d0f555e3e1a7fce9c80c6c2a8a062d0a64
7be31fc4b42c8776cd4b6517c4aa939a398b110c052e30a8eb746c476ee0dfc8
8866fbd8fdc816d20add8dbfeabcac6e0dac0f41a4e4822ab1f51b54e4bc178f
4eb7c07670704e425fd99205b8efd2f3fbccb200760127b1188fab5d7be84b06
87d779bfbe63e3b1db13f34ab9205d9f06312046e77737a05c070f1b808b5c80
9aa9e9945c52e4bf46d5b328030b42dbfba641cc0a39e6ace09361371d25fb3e
e78507a6fadfc2b6cd4a06851488b001d8b40a1ceb647b7d9debcbff6c375a13
73540d114e9bca1377b83192209bdb70824784b23da425bc7c8cca3ba0845d14
45e732d4e2327348f3a1d1f2499753bd5786ef80ef9a47c7293eedeef64d21ef
4ec47e847303fb963deff2f873efc8f2cfef263bdc40c34cb9aa80584e1ffd03
efe811cd6e12e4e6cdbda8a1ddc2df5f8f86462311425bae494f087f28524721
43e8bf6f53fb68f6063961b07dd34b0a4ebb4aba6b0f989f5955c4fe10088cae
6f7531a6b699f9e9dd34b2fc3ae102409bc97775b7bdc1e0028042e7c27fb8ed
c43c611b3ffd91de6a08a44700dd9b2d241488710e7cfabf974db0fd8a8f9b68
fee885b69d2cac23f6531af4f69c6f58e0d1ce620c47578c0bf4d3f3435df6ba
67556efaa65ec7bb518c76e840747c8454d0538f36134ee8beb0bd24a616a42d
fc826926ff9608def9b1c433d6e9ad8fcf1e11543a9595795f69c6630b60f581
156d98ce7e53124ceb06ce7e6e8054c6849aa2039b96cde7f9eac19b036b44a7
339fdc7e19b200cb062df86bdf0e022c949abacd7db8dbdae0a92a6d9ba8d530
EOF

出力 0da9cb1015db6852c07fbdc2389ad83ee8b1898a147932620753ce82d3c0709c

openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
  -kdfopt hexkey:06b849eff15c46a4401e154110a2e63a14f7333237260156f7f1b67ca3305198 \
  -kdfopt 'prefix:tls13 ' -kdfopt 'label:c hs traffic' \
  -kdfopt hexdata:0da9cb1015db6852c07fbdc2389ad83ee8b1898a147932620753ce82d3c0709c \
  TLS13-KDF | tohex

出力 8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623

openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
  -kdfopt hexkey:06b849eff15c46a4401e154110a2e63a14f7333237260156f7f1b67ca3305198 \
  -kdfopt 'prefix:tls13 ' -kdfopt 'label:s hs traffic' \
  -kdfopt hexdata:0da9cb1015db6852c07fbdc2389ad83ee8b1898a147932620753ce82d3c0709c \
  TLS13-KDF | tohex

出力 46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47f

計算方法ハンドシェイクのレコード鍵

各トラフィックシークレットは、HKDF-Expand-Label によって AES-128-GCM の鍵と 12 バイトの write_iv に展開されます。これらの鍵で、EncryptedExtensions 以降のすべてのハンドシェイクレコードを復号します。レコードごとの nonce は、write_iv とレコードのシーケンス番号の XOR です。

定義
write_key = HKDF-Expand-Label(traffic_secret, "key", "", 16)     AES-128-GCM   RFC 9846 §7.3
write_iv  = HKDF-Expand-Label(traffic_secret, "iv",  "", 12)
nonce     = write_iv XOR uint96(seq)                             RFC 9846 §5.3
このセッションの値
「key」の HkdfLabel
001009746c733133206b657900
サーバーのハンドシェイク鍵(AES-128)
5ed29e03b3495fd20354ed3f2421640a
サーバーのハンドシェイク write_iv
e465385b2d22f07ffc6a4079
クライアントのハンドシェイク鍵(AES-128)
96fa01469d090b2a8f4ef6b9fa5e0784
クライアントのハンドシェイク write_iv
9f45d2e464131048897a81c6
これで証明されること
    自分で再現する
    openssl kdf -binary -keylen 16 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47f \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:key' \
      TLS13-KDF | tohex

    出力 5ed29e03b3495fd20354ed3f2421640a

    openssl kdf -binary -keylen 12 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47f \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:iv' \
      TLS13-KDF | tohex

    出力 e465385b2d22f07ffc6a4079

    openssl kdf -binary -keylen 16 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623 \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:key' \
      TLS13-KDF | tohex

    出力 96fa01469d090b2a8f4ef6b9fa5e0784

    openssl kdf -binary -keylen 12 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623 \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:iv' \
      TLS13-KDF | tohex

    出力 9f45d2e464131048897a81c6

    サーバー → クライアントレコード 3 · 通信上のサイズ 6 バイト

    ChangeCipherSpec

    何の働きもしない 0x01 の 1 バイトです。TLS 1.3 がこれを残しているのは、ハンドシェイクをミドルボックスから TLS 1.2 のように見せるためだけです。そうしないと、ミドルボックスが接続を切断してしまいます(RFC 9846 付録 E.4)。

    通信上のバイト列 · 6 バイト

    • この 1 バイトは、OpenSSL が -msg を付けてこのレコードについて出力した内容と一致します。
    サーバー → クライアントレコード 4 · 通信上のサイズ 28 バイト

    EncryptedExtensions

    最初の暗号化されたメッセージで、鍵の確立に必要のないサーバーの拡張を運びます。今回は該当する拡張がないためメッセージは空ですが、すでにサーバーのハンドシェイク鍵で保護されており、鍵スケジュールが機能していることを証明しています。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのハンドシェイクトラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    5ed29e03b3495fd20354ed3f2421640a
    初期化ベクトルwrite_iv
    e465385b2d22f07ffc6a4079
    シーケンス番号 0 の noncenonce
    e465385b2d22f07ffc6a4079
    追加データ:レコードヘッダーadditional_data
    1703030017
    認証タグtag
    55c0c9b55b240c731db30884422ef691
    • GCM タグの検証に成功し、シーケンス番号 0 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 6 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 28 バイト

    復号した内容 · 7 バイト

    サーバー → クライアントレコード 5 · 通信上のサイズ 524 バイト

    Certificate

    サーバーの証明書チェーンで、ハンドシェイク鍵で暗号化されています。今回は ECDSA P-256 の証明書 1 枚だけです。このページでは、証明書を公開鍵の 2 つの座標に至るまで解析し、この証明書に対するデモ用 CA の署名を検証します。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのハンドシェイクトラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    5ed29e03b3495fd20354ed3f2421640a
    初期化ベクトルwrite_iv
    e465385b2d22f07ffc6a4079
    シーケンス番号 1 の noncenonce
    e465385b2d22f07ffc6a4078
    追加データ:レコードヘッダーadditional_data
    1703030207
    認証タグtag
    01e58b85a9daa69fa78913be25313b55
    • GCM タグの検証に成功し、シーケンス番号 1 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 502 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 524 バイト

    復号した内容 · 503 バイト

    計算方法証明書チェーン

    サーバーの身元は、ごく普通の署名のチェーンによって保証されます。デモ用 CA がサーバー証明書の tbsCertificate のバイト列に署名しました。OpenSSL が表示する「Verification: OK」は、その署名を CA の公開鍵で検証し、さらに名前と有効期間を確認した結果を表しています。

    定義
    e     = SHA-256(tbsCertificate)
    valid = ECDSA-P256-Verify(Q_CA, e, (r, s))         ecdsa-with-SHA256   RFC 5758 §3.2
    このセッションの値
    デモ用 CA の公開鍵(非圧縮形式の P-256 の点)
    04136596262dfe32c00a117a326530235df0416dcfb8300ac1e76b4ceaa7c3417ab65dbcce2950dc2616c49bea9c842dcc4f7d2fe1a2157adc6e4900e72b78cf64
    SHA-256(tbsCertificate)
    68ec291c24ed1fc272a0439a31cd0d28307258ca08f3cc67bde7fa115516e293
    署名値 r
    5f1c8dec01971b89bab6c887166550128ee211099c2ecbe17c0efd5d1c2d5e71
    署名値 s
    323909f5ef7bb5a0743701eccfbe4ffbc7fdfefbd12d33733c05541d8d87c86b
    これで証明されること
    • 証明書の署名は CA(O=ecdsa.com, CN=Illustrated PQ TLS Demo CA)に対して正しく検証されます。
    • CA 証明書は O=ecdsa.com, CN=Illustrated PQ TLS Demo CA による自己署名です。
    • Certificate メッセージ内の証明書は、キャプチャで記録したものとバイト単位で完全に一致します。
    • 証明書の subjectAltName は pq-tls.example を含んでいます。
    • キャプチャ時刻 2026-10-20T11:32:29Z は、証明書の有効期間内です。
    • OpenSSL の s_client は「Verification: OK」と、検証済みのピア名を出力しました。
    自分で再現する
    cat > ca.pem <<'EOF'
    -----BEGIN CERTIFICATE-----
    MIIBtjCCAVygAwIBAgIUPfq2HpUguo3lvTcCMSTFcL+tV1swCgYIKoZIzj0EAwIw
    OTESMBAGA1UECgwJZWNkc2EuY29tMSMwIQYDVQQDDBpJbGx1c3RyYXRlZCBQUSBU
    TFMgRGVtbyBDQTAeFw0yNjEwMjAxMTMyMjlaFw0yNzEwMjAxMTMyMjlaMDkxEjAQ
    BgNVBAoMCWVjZHNhLmNvbTEjMCEGA1UEAwwaSWxsdXN0cmF0ZWQgUFEgVExTIERl
    bW8gQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQTZZYmLf4ywAoRejJlMCNd
    8EFtz7gwCsHna0zqp8NBerZdvM4pUNwmFsSb6pyELcxPfS/hohV63G5JAOcreM9k
    o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
    1K3Yjw1jXxL5EZzxdBQ4jybTtAcwCgYIKoZIzj0EAwIDSAAwRQIgYKgqZkMtJeI9
    K8K7qZ2tdO5RPkRq7VYFUSdJZZxMNKACIQDs0idPxo5cjXaIzPUz3GNZeZGlQ0s9
    vjbIEmIss3iqJw==
    -----END CERTIFICATE-----
    EOF
    cat > server.pem <<'EOF'
    -----BEGIN CERTIFICATE-----
    MIIB5TCCAYygAwIBAgIUGfvagR77OCskKbwCCGme9dcUymUwCgYIKoZIzj0EAwIw
    OTESMBAGA1UECgwJZWNkc2EuY29tMSMwIQYDVQQDDBpJbGx1c3RyYXRlZCBQUSBU
    TFMgRGVtbyBDQTAeFw0yNjEwMjAxMTMyMjlaFw0yNjEyMDYxMTMyMjlaMBkxFzAV
    BgNVBAMMDnBxLXRscy5leGFtcGxlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
    vkyKek8iRLRJMhTwPtyzIi6agRiN4WF05FHlTfFE91h7RW5dsCsVIaxyYsvRhUQ/
    dZJrO8Vm3mzr9OWSBf4rz6OBkTCBjjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQE
    AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAZBgNVHREEEjAQgg5wcS10bHMuZXhh
    bXBsZTAdBgNVHQ4EFgQUQsmdGy/ZbmpLy3vpYLmOhLcQs/4wHwYDVR0jBBgwFoAU
    1K3Yjw1jXxL5EZzxdBQ4jybTtAcwCgYIKoZIzj0EAwIDRwAwRAIgXxyN7AGXG4m6
    tsiHFmVQEo7iEQmcLsvhfA79XRwtXnECIDI5CfXve7WgdDcB7M++T/vH/f770S0z
    czwFVB2Nh8hr
    -----END CERTIFICATE-----
    EOF
    openssl verify -attime 1792495949 -CAfile ca.pem -verify_hostname pq-tls.example server.pem

    出力 server.pem: OK

    サーバー → クライアントレコード 6 · 通信上のサイズ 102 バイト

    CertificateVerify

    サーバーは、ここまでのトランスクリプト全体に証明書の秘密鍵で署名し、その鍵を保持していることを証明します。この署名は、Certificate メッセージの公開鍵で検証されます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのハンドシェイクトラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    5ed29e03b3495fd20354ed3f2421640a
    初期化ベクトルwrite_iv
    e465385b2d22f07ffc6a4079
    シーケンス番号 2 の noncenonce
    e465385b2d22f07ffc6a407b
    追加データ:レコードヘッダーadditional_data
    1703030061
    認証タグtag
    a7d50cfc2454b60756d83834baef0ca7
    • GCM タグの検証に成功し、シーケンス番号 2 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 80 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 102 バイト

    復号した内容 · 81 バイト

    計算方法CertificateVerify の署名

    有効な証明書だけでは不十分です。サーバーは、対応する秘密鍵を保持していることを、まさに今、このトランスクリプトに対して証明しなければなりません。サーバーは固定のコンテキスト文字列と、Certificate メッセージまでのトランスクリプト全体のハッシュ値に署名し、クライアントは受け取ったばかりの公開鍵でその署名を検証します。

    定義
    content = 0x20 × 64 ‖ "TLS 1.3, server CertificateVerify" ‖ 0x00 ‖ SHA-256(ClientHello … Certificate)
    valid   = ECDSA-P256-Verify(Q_server, SHA-256(content), (r, s))      ecdsa_secp256r1_sha256
    このセッションの値
    SHA-256(ClientHello … Certificate)
    af76fc7ac4f19eb2af3f584874eb0cd0429a06a15cf60faefa739b3fa4eb8aad
    署名対象の内容(130 バイト)
    20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544c5320312e332c2073657276657220436572746966696361746556657269667900af76fc7ac4f19eb2af3f584874eb0cd0429a06a15cf60faefa739b3fa4eb8aad
    サーバー証明書の公開鍵(P-256 の点)
    04be4c8a7a4f2244b4493214f03edcb3222e9a81188de16174e451e54df144f7587b456e5db02b1521ac7262cbd185443f75926b3bc566de6cebf4e59205fe2bcf
    署名値 r
    e2a43fd0b309c60cd461d2e842bf89926c1d271a3f6e79ee5776cb8cc4f8ddbc
    署名値 s
    a9667486294b49473c7bc82723f305694cdd7e8f591b587e2b75b909da995827
    これで証明されること
    • ecdsa_secp256r1_sha256 は、クライアントが signature_algorithms で提示した方式の 1 つです。
    • CertificateVerify の署名は、署名対象の内容について証明書の公開鍵で正しく検証されます。
    • 公開されているデモ用の署名鍵は、この証明書の秘密鍵です。
    • OpenSSL の s_client は、ピアの署名タイプとして ecdsa_secp256r1_sha256 を出力しました。
    自分で再現する
    unhex > server.der <<'EOF'
    308201e53082018ca003020102021419fbda811efb382b2429bc0208699ef5d7
    14ca65300a06082a8648ce3d040302303931123010060355040a0c0965636473
    612e636f6d3123302106035504030c1a496c6c75737472617465642050512054
    4c532044656d6f204341301e170d3236313032303131333232395a170d323631
    3230363131333232395a30193117301506035504030c0e70712d746c732e6578
    616d706c653059301306072a8648ce3d020106082a8648ce3d03010703420004
    be4c8a7a4f2244b4493214f03edcb3222e9a81188de16174e451e54df144f758
    7b456e5db02b1521ac7262cbd185443f75926b3bc566de6cebf4e59205fe2bcf
    a3819130818e300c0603551d130101ff04023000300e0603551d0f0101ff0404
    0302078030130603551d25040c300a06082b0601050507030130190603551d11
    04123010820e70712d746c732e6578616d706c65301d0603551d0e0416041442
    c99d1b2fd96e6a4bcb7be960b98e84b710b3fe301f0603551d23041830168014
    d4add88f0d635f12f9119cf17414388f26d3b407300a06082a8648ce3d040302
    034700304402205f1c8dec01971b89bab6c887166550128ee211099c2ecbe17c
    0efd5d1c2d5e710220323909f5ef7bb5a0743701eccfbe4ffbc7fdfefbd12d33
    733c05541d8d87c86b
    EOF
    openssl x509 -inform DER -in server.der -pubkey -noout > server-pub.pem
    { printf '%64s' ''; printf 'TLS 1.3, server CertificateVerify\000'; printf '%s' af76fc7ac4f19eb2af3f584874eb0cd0429a06a15cf60faefa739b3fa4eb8aad | unhex; } > signed.bin
    printf '%s' 3046022100e2a43fd0b309c60cd461d2e842bf89926c1d271a3f6e79ee5776cb8cc4f8ddbc022100a9667486294b49473c7bc82723f305694cdd7e8f591b587e2b75b909da995827 | unhex > signature.der
    openssl dgst -sha256 -verify server-pub.pem -signature signature.der signed.bin

    出力 Verified OK

    printf '%s' 3031020101042011aa8fac7486c821d6c57baa03c3a3e119a635f33aaa2043bba12d7f20a40a99a00a06082a8648ce3d030107 | unhex > server-key.der
    openssl ec -inform DER -in server-key.der -pubout -outform DER 2>/dev/null | tail -c 65 | tohex

    出力 04be4c8a7a4f2244b4493214f03edcb3222e9a81188de16174e451e54df144f7587b456e5db02b1521ac7262cbd185443f75926b3bc566de6cebf4e59205fe2bcf

    サーバー → クライアントレコード 7 · 通信上のサイズ 58 バイト

    Finished(サーバー)

    ハンドシェイクのトランスクリプト全体に対する HMAC で、ハンドシェイク鍵を持つ者しか得られないシークレットを鍵にしています。これでハンドシェイクのサーバー側が締めくくられます。クライアントがこれを検証すると、サーバーが認証され、鍵も確認されます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのハンドシェイクトラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    5ed29e03b3495fd20354ed3f2421640a
    初期化ベクトルwrite_iv
    e465385b2d22f07ffc6a4079
    シーケンス番号 3 の noncenonce
    e465385b2d22f07ffc6a407a
    追加データ:レコードヘッダーadditional_data
    1703030035
    認証タグtag
    19198cc6e0426e75d2b219994f1a0467
    • GCM タグの検証に成功し、シーケンス番号 3 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 36 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 58 バイト

    復号した内容 · 37 バイト

    計算方法サーバーの Finished

    サーバーの Finished は、ここまでのトランスクリプト全体に対する HMAC で、ハンドシェイクトラフィックシークレットから導出したシークレットを鍵にしています。同じ鍵を計算した当事者しかこれを生成できないため、これを検証することで、サーバーの鍵と、トランスクリプトが改ざんされていないことの両方を確認できます。

    定義
    finished_key = HKDF-Expand-Label(s_hs_traffic, "finished", "", 32)
    verify_data  = HMAC-SHA256(finished_key, SHA-256(ClientHello … CertificateVerify))
    このセッションの値
    サーバーの finished_key
    0506725331e4fe72ae23212deef946f34556d38825410fb427bf071ee67951a1
    SHA-256(ClientHello … CertificateVerify)
    9e49537a2a313824d7825cbef2384d2a12ae91c8e09dcf3ac42fcd2393dae658
    verify_data = HMAC(finished_key, トランスクリプト)
    37187ea7e2a1e27e9d42e736edaac65dccc6dc9d03e7b862019dac0212000280
    これで証明されること
    • 再計算した verify_data は、Finished メッセージ内の値と一致します。
    自分で再現する
    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47f \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:finished' \
      TLS13-KDF | tohex

    出力 0506725331e4fe72ae23212deef946f34556d38825410fb427bf071ee67951a1

    printf '%s' 9e49537a2a313824d7825cbef2384d2a12ae91c8e09dcf3ac42fcd2393dae658 | unhex | openssl mac -binary -digest SHA256 -macopt hexkey:0506725331e4fe72ae23212deef946f34556d38825410fb427bf071ee67951a1 HMAC | tohex

    出力 37187ea7e2a1e27e9d42e736edaac65dccc6dc9d03e7b862019dac0212000280

    計算方法アプリケーショントラフィックシークレット

    サーバー側の処理が終わると、鍵スケジュールはメインシークレットに進み、サーバーの Finished までのトランスクリプトに基づいてアプリケーショントラフィックシークレットを導出し、あわせてエクスポーターシークレットも導出します。アプリケーションデータはすべて、これらから得た鍵で保護されます。OpenSSL はこの 3 つをすべてログに記録しており、このページの値はそれと一致します。

    定義
    derived         = Derive-Secret(handshake_secret, "derived", "")
    main_secret     = HKDF-Extract(salt = derived, IKM = 0)          RFC 8446: master_secret
    c_ap_traffic    = Derive-Secret(main_secret, "c ap traffic", ClientHello … server Finished)
    s_ap_traffic    = Derive-Secret(main_secret, "s ap traffic", ClientHello … server Finished)
    exporter_secret = Derive-Secret(main_secret, "exp master",   ClientHello … server Finished)
    
    write_key = HKDF-Expand-Label(c_ap_traffic | s_ap_traffic, "key", "", 16)
    write_iv  = HKDF-Expand-Label(c_ap_traffic | s_ap_traffic, "iv",  "", 12)
    このセッションの値
    導出シークレット
    cf180a268bf009d2b86962078fe3a21b7946acf2bf393b3a1672276547e88a81
    main_secret(RFC 8446 では master_secret)
    535beb1d564ea9c45dce195f453307900f8548a29f3889d55aea446547724e1b
    SHA-256(ClientHello … サーバーの Finished)
    5a0c617c785aacf18760a5272a8424b31be9504bf266939c6d0c7a008ca49adb
    client_application_traffic_secret_0
    822f761f7f0218c1566132cf545c5f7b632ec3c9e5c454254c1ab051f2a92dec
    server_application_traffic_secret_0
    556780cd0561751359abc293e490bbdff13dee06fe016160961add756502e56d
    exporter_master_secret
    fdf77404b2ef159adf31a636a7efb2a693ed0491d0362d0eb270ad93bd96592e
    サーバーのアプリケーション鍵(AES-128)
    f2c6df22d8075b76928d37545f335a64
    サーバーのアプリケーション write_iv
    d3bd0df90c48d9fbb108cffb
    クライアントのアプリケーション鍵(AES-128)
    58391c840aa46afae3552c38ad15b95b
    クライアントのアプリケーション write_iv
    68020e323a5ac9a9f0a4d49c
    これで証明されること
    • 両方の SSLKEYLOGFILE の CLIENT_TRAFFIC_SECRET_0 行と一致します。
    • 両方の SSLKEYLOGFILE の SERVER_TRAFFIC_SECRET_0 行と一致します。
    • 両方の SSLKEYLOGFILE の EXPORTER_SECRET 行と一致します。
    自分で再現する
    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:06b849eff15c46a4401e154110a2e63a14f7333237260156f7f1b67ca3305198 \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:derived' \
      -kdfopt hexdata:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 \
      TLS13-KDF | tohex

    出力 cf180a268bf009d2b86962078fe3a21b7946acf2bf393b3a1672276547e88a81

    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXTRACT_ONLY \
      -kdfopt hexsalt:cf180a268bf009d2b86962078fe3a21b7946acf2bf393b3a1672276547e88a81 \
      -kdfopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 \
      HKDF | tohex

    出力 535beb1d564ea9c45dce195f453307900f8548a29f3889d55aea446547724e1b

    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:535beb1d564ea9c45dce195f453307900f8548a29f3889d55aea446547724e1b \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:c ap traffic' \
      -kdfopt hexdata:5a0c617c785aacf18760a5272a8424b31be9504bf266939c6d0c7a008ca49adb \
      TLS13-KDF | tohex

    出力 822f761f7f0218c1566132cf545c5f7b632ec3c9e5c454254c1ab051f2a92dec

    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:535beb1d564ea9c45dce195f453307900f8548a29f3889d55aea446547724e1b \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:s ap traffic' \
      -kdfopt hexdata:5a0c617c785aacf18760a5272a8424b31be9504bf266939c6d0c7a008ca49adb \
      TLS13-KDF | tohex

    出力 556780cd0561751359abc293e490bbdff13dee06fe016160961add756502e56d

    クライアント → サーバーレコード 8 · 通信上のサイズ 6 バイト

    ChangeCipherSpec

    クライアント側の対応するダミーレコードです。ミドルボックスへの配慮という同じ理由で送られます。

    通信上のバイト列 · 6 バイト

    • この 1 バイトは、OpenSSL が -msg を付けてこのレコードについて出力した内容と一致します。
    クライアント → サーバーレコード 9 · 通信上のサイズ 58 バイト

    Finished(クライアント)

    サーバーの Finished までのトランスクリプトに対するクライアントの HMAC です。これでハンドシェイクが完了します。この時点で両者は認証済みとなり、アプリケーション鍵に切り替えます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。クライアントのハンドシェイクトラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    96fa01469d090b2a8f4ef6b9fa5e0784
    初期化ベクトルwrite_iv
    9f45d2e464131048897a81c6
    シーケンス番号 0 の noncenonce
    9f45d2e464131048897a81c6
    追加データ:レコードヘッダーadditional_data
    1703030035
    認証タグtag
    f3a8eac5bf26a9ac3aaca3a4afebd656
    • GCM タグの検証に成功し、シーケンス番号 0 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 36 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 58 バイト

    復号した内容 · 37 バイト

    計算方法クライアントの Finished

    クライアントは自身の Finished で応答します。これは、サーバーの Finished までのトランスクリプトに対する HMAC で、鍵はクライアントのハンドシェイクトラフィックシークレットから導出されます。サーバーがこれを検証すると、ハンドシェイクが完了し、双方向の認証が済んだことになります。

    定義
    finished_key = HKDF-Expand-Label(c_hs_traffic, "finished", "", 32)
    verify_data  = HMAC-SHA256(finished_key, SHA-256(ClientHello … server Finished))
    このセッションの値
    クライアントの finished_key
    780c2e56af642c22fc8ae057276cd0d8bde149879a68ccb28064b4cf6b677a73
    SHA-256(ClientHello … サーバーの Finished)
    5a0c617c785aacf18760a5272a8424b31be9504bf266939c6d0c7a008ca49adb
    verify_data = HMAC(finished_key, トランスクリプト)
    e1c250d75d6df181b0df677983ec692a146f428d982a0b4281cca28607ac7fb1
    これで証明されること
    • 再計算した verify_data は、Finished メッセージ内の値と一致します。
    自分で再現する
    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623 \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:finished' \
      TLS13-KDF | tohex

    出力 780c2e56af642c22fc8ae057276cd0d8bde149879a68ccb28064b4cf6b677a73

    printf '%s' 5a0c617c785aacf18760a5272a8424b31be9504bf266939c6d0c7a008ca49adb | unhex | openssl mac -binary -digest SHA256 -macopt hexkey:780c2e56af642c22fc8ae057276cd0d8bde149879a68ccb28064b4cf6b677a73 HMAC | tohex

    出力 e1c250d75d6df181b0df677983ec692a146f428d982a0b4281cca28607ac7fb1

    サーバー → クライアントレコード 10 · 通信上のサイズ 239 バイト

    NewSessionTicket

    ハンドシェイク後にサーバーから贈られるもので、クライアントが次回、完全な鍵交換なしでセッションを再開するために提示できるチケットです。このチケットが表す事前共有鍵は、再開用シークレットとチケットの nonce から導出されます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのアプリケーショントラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    f2c6df22d8075b76928d37545f335a64
    初期化ベクトルwrite_iv
    d3bd0df90c48d9fbb108cffb
    シーケンス番号 0 の noncenonce
    d3bd0df90c48d9fbb108cffb
    追加データ:レコードヘッダーadditional_data
    17030300ea
    認証タグtag
    da58bb9fa9790e3f945cd811785d9ab3
    • GCM タグの検証に成功し、シーケンス番号 0 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 217 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 239 バイト

    復号した内容 · 218 バイト

    計算方法再開用の事前共有鍵

    チケットは、それが指すシークレットがなければ役に立ちません。その事前共有鍵は、再開用シークレット(これ自体はクライアントの Finished までのトランスクリプトから得られます)とチケットの nonce から導出されます。OpenSSL は PSK とチケットの両方を出力しました。このページでは PSK を再計算し、OpenSSL の出力と照合します。

    定義
    resumption_secret = Derive-Secret(main_secret, "res master", ClientHello … client Finished)
    PSK               = HKDF-Expand-Label(resumption_secret, "resumption", ticket_nonce, 32)
    このセッションの値
    SHA-256(ClientHello … クライアントの Finished)
    b014275674636844d99285ea448e9c45e92acb5bc9bf249fa8d94b7796eb1211
    resumption_master_secret
    d013100d5c7e78c4d128a445969ce4ed1ddc3cd63a7f63bddcd82728d5129f8a
    NewSessionTicket の ticket_nonce
    0000000000000000
    再開用の PSK
    d1039d8e602a12c21ed17aa291499d5f1ef30eda117144c3591d5fccd74f7c0d
    これで証明されること
    • 再計算した再開用の PSK は、s_client が出力したものと一致します。
    • チケットの 192 バイトは、s_client が出力したチケットと一致します。
    自分で再現する
    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:535beb1d564ea9c45dce195f453307900f8548a29f3889d55aea446547724e1b \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:res master' \
      -kdfopt hexdata:b014275674636844d99285ea448e9c45e92acb5bc9bf249fa8d94b7796eb1211 \
      TLS13-KDF | tohex

    出力 d013100d5c7e78c4d128a445969ce4ed1ddc3cd63a7f63bddcd82728d5129f8a

    openssl kdf -binary -keylen 32 -kdfopt digest:SHA256 -kdfopt mode:EXPAND_ONLY \
      -kdfopt hexkey:d013100d5c7e78c4d128a445969ce4ed1ddc3cd63a7f63bddcd82728d5129f8a \
      -kdfopt 'prefix:tls13 ' -kdfopt 'label:resumption' \
      -kdfopt hexdata:0000000000000000 \
      TLS13-KDF | tohex

    出力 d1039d8e602a12c21ed17aa291499d5f1ef30eda117144c3591d5fccd74f7c0d

    クライアント → サーバーレコード 11 · 通信上のサイズ 49 バイト

    アプリケーションデータ(リクエスト)

    クライアントのアプリケーション鍵で保護された、最初の実際のペイロードです。中身は 1 行の HTTP リクエストです。ここからは、レコードのコンテンツタイプは暗号化の内側に隠されます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。クライアントのアプリケーショントラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    58391c840aa46afae3552c38ad15b95b
    初期化ベクトルwrite_iv
    68020e323a5ac9a9f0a4d49c
    シーケンス番号 0 の noncenonce
    68020e323a5ac9a9f0a4d49c
    追加データ:レコードヘッダーadditional_data
    170303002c
    認証タグtag
    44f8869fbc72bc4a095024412310f30e
    • GCM タグの検証に成功し、シーケンス番号 0 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号したコンテンツタイプとバイト列は、OpenSSL の -msg のトレースと一致します。

    通信上のバイト列 · 49 バイト

    復号した内容 · 28 バイト

    サーバー → クライアントレコード 12 · 通信上のサイズ 130 バイト

    アプリケーションデータ(レスポンス)

    サーバーのアプリケーション鍵で保護された応答で、クライアントが要求したファイルを含む HTTP レスポンスです。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのアプリケーショントラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    f2c6df22d8075b76928d37545f335a64
    初期化ベクトルwrite_iv
    d3bd0df90c48d9fbb108cffb
    シーケンス番号 1 の noncenonce
    d3bd0df90c48d9fbb108cffa
    追加データ:レコードヘッダーadditional_data
    170303007d
    認証タグtag
    152ee3122b64d6188a96491377f3ba2d
    • GCM タグの検証に成功し、シーケンス番号 1 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号したコンテンツタイプとバイト列は、OpenSSL の -msg のトレースと一致します。

    通信上のバイト列 · 130 バイト

    復号した内容 · 109 バイト

    サーバー → クライアントレコード 13 · 通信上のサイズ 24 バイト

    アラート(close_notify)

    サーバーが正常な切断を通知します。アラートも暗号化されるため、接続の終了さえも秘匿されます。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。サーバーのアプリケーショントラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    f2c6df22d8075b76928d37545f335a64
    初期化ベクトルwrite_iv
    d3bd0df90c48d9fbb108cffb
    シーケンス番号 2 の noncenonce
    d3bd0df90c48d9fbb108cff9
    追加データ:レコードヘッダーadditional_data
    1703030013
    認証タグtag
    d1a180f9a2947d57978b590f4ea3faa8
    • GCM タグの検証に成功し、シーケンス番号 2 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 2 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 24 バイト

    復号した内容 · 3 バイト

    クライアント → サーバーレコード 14 · 通信上のサイズ 24 バイト

    アラート(close_notify)

    クライアントが切断に応じ、接続が終了します。

    レコードの保護

    このレコードは、暗号化されたデータを運ぶ application_data レコードです。クライアントのアプリケーショントラフィックの鍵と、以下のレコードごとの nonce を使って、AES-128-GCM で復号されます。16 バイトの認証タグは、5 バイトのレコードヘッダーを追加データとして含めて検証されます(RFC 9846 §5.2)。

    AES-128-GCM の鍵write_key
    58391c840aa46afae3552c38ad15b95b
    初期化ベクトルwrite_iv
    68020e323a5ac9a9f0a4d49c
    シーケンス番号 1 の noncenonce
    68020e323a5ac9a9f0a4d49d
    追加データ:レコードヘッダーadditional_data
    1703030013
    認証タグtag
    daf6ae55a253a373f23464e913920d47
    • GCM タグの検証に成功し、シーケンス番号 1 でレコードが正しく復号されます。
    • 復号した平文を再び暗号化すると、キャプチャしたレコードが正確に再現されます。
    • 復号した 2 バイトは、OpenSSL が -msg を付けてこのメッセージについて出力した内容と一致します。

    通信上のバイト列 · 24 バイト

    復号した内容 · 3 バイト

    OpenSSL の鍵ログとの照合

    OpenSSL は両側で SSLKEYLOGFILE を書き出しました。これは、Wireshark がキャプチャを復号するために読み込む形式です。このファイルにある 5 つのシークレットはそれぞれ、上で計算したトラフィックシークレットのいずれかに当たります。そのため、これらがすべて一致することが、このページの鍵スケジュールが実際の接続で使われたものと同じであることの証明になります。

    SSLKEYLOGFILE のラベルシークレット(16 進数)このページでの名称
    SERVER_HANDSHAKE_TRAFFIC_SECRET46925f99fa426e5dc105f5eaccfca048e1e89a93370b07cb9ab447518eb8f47fserver_handshake_traffic_secret
    EXPORTER_SECRETfdf77404b2ef159adf31a636a7efb2a693ed0491d0362d0eb270ad93bd96592eexporter_master_secret
    SERVER_TRAFFIC_SECRET_0556780cd0561751359abc293e490bbdff13dee06fe016160961add756502e56dserver_application_traffic_secret_0
    CLIENT_HANDSHAKE_TRAFFIC_SECRET8b0beea15604895fd4408c32b82684d347fa5e2422644c1d93978db70666f623client_handshake_traffic_secret
    CLIENT_TRAFFIC_SECRET_0822f761f7f0218c1566132cf545c5f7b632ec3c9e5c454254c1ab051f2a92decclient_application_traffic_secret_0

    キャプチャの方法

    このハンドシェイクは模擬ではなく本物です。イメージを固定した 1 つのコンテナー内で、2 つの OpenSSL プロセスがループバック経由で TLS 1.3 で通信し、そのパケットを通信経路上で記録しました。キャプチャをやり直すと、同じ構造を持つ新しいセッションが生成されます。

    コンテナーイメージ
    node:24.22.0-trixie@sha256:ff054b356ab93ed540518ca23cfcd9840e5256dd04db5a8d8afc43f4ecc7c9f8
    OpenSSL
    OpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
    キャプチャ日時
    2026-10-20T11:32:29Z
    サーバー
    openssl s_server -accept 4433 -naccept 1 -WWW -cert server.crt -key server.key -tls1_3 -ciphersuites TLS_AES_128_GCM_SHA256 -groups X25519MLKEM768 -num_tickets 1 -keylogfile server-keylog.txt
    クライアント
    openssl s_client -connect 127.0.0.1:4433 -servername pq-tls.example -verify_hostname pq-tls.example -CAfile ca.crt -verify_return_error -tls1_3 -keylogfile client-keylog.txt -ign_eof -msg -msgfile client-msg.txt

    各コマンドは、次の 2 つのヘルパーを除けばそれだけで完結しています。先にこの 2 つを貼り付けてください(OpenSSL 3.5 のほか、Linux と macOS のどちらにも入っている Perl と od を使います):

    unhex() { perl -0777 -ne 's/\s+//g; print pack("H*", $_)'; }
    tohex() { od -An -v -tx1 | tr -d ' \n'; echo; }

    仕様

    ハイブリッド鍵交換は RFC 10024(X25519MLKEM768)で、NIST FIPS 203 の ML-KEM-768 と RFC 7748 の X25519 を組み合わせたものです。ハイブリッドの構成とキーシェアの順序は RFC 9954 に従います。それ以外はすべて RFC 9846(RFC 8446 を廃止)で規定された TLS 1.3 のとおりで、鍵スケジュールのトレースは RFC 8448 と照合しています。コードポイントとアルゴリズムの名前は、IANA の TLS レジストリとオブジェクト識別子のレジストリから取っています。

    このページの形式(実際の接続を取り上げ、すべてのバイトを解説する形式)は、Michael Driscoll 氏による『The Illustrated TLS 1.3 Connection』(MIT ライセンス)にならっています。ここに掲載したキャプチャ、解説、コードは当サイトが独自に用意したもので、このページではさらにポスト量子鍵交換を扱っています。