🚂

TLS1.3の中身をみよう(RFC8448)

2021/07/18に公開2

はじめに

TLS1.3の仕様は、「RFC8446 - The Transport Layer Security (TLS) Protocol Version 1.3」で規定されています。「RFC8448 - Example Handshake Traces for TLS 1.3」には、鍵共有やパケットの暗号化など、ハンドシェイクの例が示されています。本記事では、RFC8448の「3. Simple 1-RTT Handshake」の内容を順番に追っていきます。

なお、本記事ではデジタル署名による認証については説明しておりません。

本記事のコード(Jupyter Notebook)を公開してみました。Google Colaboratoryで実行できます。ただし、サイドチャネル攻撃等に対するセキュリティは考慮していません。ご了承ください。
https://colab.research.google.com/github/0a24/tls13-rfc8448/blob/main/example_rfc8448.ipynb

概要

プロトコルの概要は、RFC8446 Figure 1に記載されています。

準備

Scapy

Scapy、パケットの中身を見るのにとても便利。ですが(バイナリを直接見られるなら)無くても大丈夫です。

暗号技術

この記事で登場する暗号技術は5つです。
それぞれの暗号技術の中身については深くつっこみません。

  • 鍵共有: ECDH X25519
    楕円曲線ディフィー・ヘルマン鍵共有(RFC8446 ECDH)は、共通鍵の素を共有するために使用します。
    実装はeccsnacksを使いました。RFC7748に従っているようです。
    ECDH with Curve 25519 using Pythonも参考にしました。

  • ハッシュ関数: SHA-256
    hashlibを使用。

  • メッセージ認証コード: HMAC
    Hash-based Message Authentication Code。ハッシュベースのメッセージ認証コード。ざっくりいうと鍵付きハッシュ。
    こちらもhashlibを使用。

  • 共通鍵暗号: AES
    128ビットブロック暗号。だけど、後述の暗号利用モードを使えばブロックサイズはあまり気にならないと思う。(厳密には制限があった気がする)
    PyCryptodomeを使用。

  • 暗号利用モード: GCM
    AESなどと併用することで認証付き暗号として使えます。AEAD(Authenticated Encryption with Associated Data)とも呼ばれて、暗号文とおまけデータをまとめて認証ができます。
    PyCryptodomeのAESでは、引数で暗号利用モードが設定できます。

鍵スケジュール(Key Schedule)とHKDF

暗号化などに用いる鍵はSecretと呼ばれる値から導出されます。
Secretの導出の概要は、RFC8446 93ページあたりをご覧ください。

Secretや鍵の導出に使う関数(HKDF)を紹介します。

HKDFについては、Cryptographic Extraction and Key Derivation: The HKDF Schemeに詳しい話がありそうです。(読んでません)

HKDFは、Scapyに実装されているようですが今回は使用しておりません。

myhkdf.py
import hmac
import hashlib

# https://datatracker.ietf.org/doc/html/rfc5869
def HKDF_Extract(salt, IKM, hashtype):
    if hashtype == "sha256":
        PRK = hmac.new(salt, IKM, hashlib.sha256)
    elif hashtype == "sha384":
        PRK = hmac.new(salt, IKM, hashlib.sha384)
    return PRK.digest()

def HKDF_Expand(PRK, info, L, hashtype):
    T = b''
    T_i = b''
    cnt = 0
    if hashtype == "sha256":
        while len(T) < L:
            cnt += 1
            h = hmac.new(PRK, T_i + info + cnt.to_bytes(1,'big'), hashlib.sha256)
            T_i = h.digest()
            T += T_i
            
    elif hashtype == "sha384":
        while len(T) < L:
            cnt += 1
            h = hmac.new(PRK, T_i + info + cnt.to_bytes(1,'big'), hashlib.sha384)
            T_i = h.digest()
            T += T_i
    OKM = T[:L]
    return OKM

# https://datatracker.ietf.org/doc/html/rfc8446#section-7
def HKDF_Expand_Label(Secret, Label, Context, Length, hashtype):
    Label = b"tls13 " + Label
    HkdfLabel = Length.to_bytes(2,'big') + len(Label).to_bytes(1,'big') + Label + \
                len(Context).to_bytes(1,'big') + Context
    return HKDF_Expand(Secret, HkdfLabel, Length, hashtype)

def Derive_Secret(Secret, Label, Messages, hashtype):
    if hashtype == "sha256":
        Hash_length = 32
    elif hashtype == "sha384":
        Hash_length = 48
    return HKDF_Expand_Label(Secret, Label, Transcript_Hash(Messages, hashtype), Hash_length, hashtype)

# https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.1
def Transcript_Hash(Messages, hashtype):
    if type(Messages) is list:
        Messages = b''.join(Messages)
    if hashtype == "sha256":
        h = hashlib.sha256(Messages).digest()        
    elif hashtype == "sha384":
        h = hashlib.sha384(Messages).digest()
    return h

鍵の種類

たくさん鍵が登場するのでまとめておきます。
サーバの認証など触れていない部分もあるので、実際はもっとあると思います。

  • ECDH用秘密鍵
  • ECDH用公開鍵
  • HMAC生成用共通鍵
  • HMAC検証用共通鍵
  • ハンドシェイク暗号化用共通鍵
  • ハンドシェイク復号用共通鍵
  • アプリケーションデータ暗号化用共通鍵
  • アプリケーションデータ復号用共通鍵

当然ですが、クライアントが暗号化に用いる鍵とサーバが復号に用いる鍵は同じです。ただし、クライアントが暗号化(サーバが復号)に使う共通鍵と、サーバが暗号化(クライアントが復号)に使う共通鍵は異なるので注意です。HMACについてもクライアントが生成する場合とサーバが生成する場合で使う鍵が異なります。

HMACに使う共通鍵については4.4.4. Finishedに記載されています。
それ以外の共通鍵は7.3. Traffic Key Calculation

RFC8448 - 3. Simple 1-RTT Handshake

ここからが本番です。
RFC8448、3. Simple 1-RTT Handshakeの内容を順番に追っていきます。本記事に記載していない部分もありますので、RFCと見比べながら読むのがわかりやすいと思います。

今回利用するモジュールのインポートと関数の定義をしておきます。

from binascii import a2b_hex, b2a_hex
import hashlib
from Crypto.Cipher import AES
import cryptography # Scapyに必要っぽい
from scapy.layers.tls.handshake import *
from scapy.layers.tls.record_tls13 import *
from eccsnacks import curve25519
from myhkdf import *

# ECDHで使う関数
# https://datatracker.ietf.org/doc/html/rfc7748
# https://asecuritysite.com/encryption/python_25519ecdh
def decodeLittleEndian(b):
    return sum([b[i] << 8*i for i in range(32)])

def decodeScalar25519(k):
    k_list = [(b) for b in k]
    k_list[0] &= 248
    k_list[31] &= 127
    k_list[31] |= 64
    return decodeLittleEndian(k_list)

クライアント側処理①

{client} create an ephemeral x25519 key pair:

まずはECDHの鍵の生成です。
ここにはクライアントの秘密鍵と公開鍵が記載されています。
秘密鍵から公開鍵を生成してみます。以下のコードの出力とRFCに記載されている値が一致していることが確認できます。(秘密鍵はどのように決めるのでしょうか。気が向いたら調べます。)

# {client}  create an ephemeral x25519 key pair:
client_private_key = '''49 af 42 ba 7f 79 94 85 2d 71 3e f2 78
         4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05'''
client_private_key = a2b_hex(client_private_key.replace(" ","").replace("\n",""))

a = curve25519.clamp(decodeScalar25519(client_private_key))
A = curve25519.X25519(a, 9)
client_public_key = A.to_bytes(32,'little') # エンディアンが逆
print("client_public_key:", b2a_hex(client_public_key))
# client_public_key: b'99381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c'

{client} construct a ClientHello handshake message:

ClientHelloです。クライアントはネゴシエーション無しに公開鍵を送り付けます。(サーバに断られたら提示されたアルゴリズムでやり直す。(HelloRetryRequest))
Scapyを使ってみると、CipherSuiteの候補や先程導出した公開鍵が含まれているのがわかります。

# {client}  construct a ClientHello handshake message:
ClientHello = '''01 00 00 c0 03 03 cb 34 ec b1 e7 81 63
         ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83
         02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b
         00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00
         12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23
         00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2
         3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a
         af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03
         02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06
         02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01'''
ch = a2b_hex(ClientHello.replace(" ","").replace("\n",""))
tls13ch = TLS13ClientHello(ch)
tls13ch.show() # 色々見られる
print("ciphers:", tls13ch.sprintf("%ciphers%"))
# ciphers: [TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_256_GCM_SHA384]

ch_keyshare = tls13ch.ext[4]
c_pub = ch_keyshare.client_shares[0].key_exchange
print("key_exchange:", b2a_hex(c_pub))
# key_exchange: b'99381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c'
tls13ch.show()の出力

###[ TLS 1.3 Handshake - Client Hello ]###
msgtype = client_hello
msglen = 192
version = TLS 1.2
random_bytes= cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7
sidlen = 0
sid = ''
cipherslen= 6
ciphers = [TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_256_GCM_SHA384]
complen = 1
comp = null
extlen = 145
\ext
|###[ TLS Extension - Server Name ]###
| type = server_name
| len = 11
| servernameslen= 9
| servernames= [b'server']
|###[ TLS Extension - Renegotiation Indication ]###
| type = renegotiation_info
| len = 1
| reneg_conn_len= 0
| renegotiated_connection= ''
|###[ TLS Extension - Supported Groups ]###
| type = supported_groups
| len = 20
| groupslen = 18
| groups = [x25519, secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
|###[ TLS Extension - Session Ticket ]###
| type = session_ticket
| len = 0
| ticket = ''
|###[ TLS Extension - Key Share (for ClientHello) ]###
| type = key_share
| len = 38
| client_shares_len= 36
| \client_shares
| |###[ Key Share Entry ]###
| | group = x25519
| | kxlen = 32
| | key_exchange= '\x998\x1d\xe5`\xe4\xbdC\xd2=\x8eCZ}\xba\xfe\xb3\xc0nQ\xc1<\xaeMT\x13i\x1eR\x9a\xaf,'
|###[ TLS Extension - Supported Versions (for ClientHello) ]###
| type = supported_versions
| len = 3
| versionslen= 2
| versions = [TLS 1.3]
|###[ TLS Extension - Signature Algorithms ]###
| type = signature_algorithms
| len = 32
| sig_algs_len= 30
| sig_algs = [sha256+ecdsa, sha384+ecdsa, sha512+ecdsa, sha1+ecdsa, sha256+rsaepss, sha384+rsaepss, sha512+rsaepss, sha256+rsa, sha384+rsa, sha512+rsa, sha1+rsa, sha256+dsa, sha384+dsa, sha512+dsa, sha1+dsa]
|###[ TLS Extension - PSK Key Exchange Modes ]###
| type = psk_key_exchange_modes
| len = 2
| kxmodeslen= 1
| kxmodes = [psk_dhe_ke]
|###[ TLS Extension - Record Size Limit ]###
| type = record_size_limit
| len = 2
| record_size_limit= 16385

{client} send handshake record:

ClientHelloにContentType、version、ClientHelloのサイズを付けて送信します。
ここのversionは互換性のためlegacy_record_versionが記されます。(参考

# {client}  send handshake record:
ch_hs_rec_header = b'\x16' + b'\x03\x01' + len(ch).to_bytes(2,'big')
ch_hs_rec = hs_rec_header + ch
print("ClientHello record:", b2a_hex(ch_hs_rec))
# ClientHello record: b'16030100c4010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001'
TLS(ch_hs_rec).show() # 出力は省略

サーバ側処理①

{server} extract secret "early":

HKDF_Extract(HMAC)でEarly Secretを求めます。(Pre-Shared Keyがなければ)ここの入力は固定値なので出力も決まった値になります。
ハッシュ関数のアルゴリズムはClientHelloのCipherSuiteに記載されているものから選ばれるようです(ここではSHA-256)。

# {server}  extract secret "early":
salt = b'\x00'
IKM = b'\x00' * 32
early_secret = HKDF_Extract(salt, IKM, "sha256")
print("Early Secret:", b2a_hex(early_secret))
# Early Secret: b'33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a'

{server} create an ephemeral x25519 key pair:

サーバでもECDHの公開鍵を生成します。

# {server}  create an ephemeral x25519 key pair:
server_private_key = '''b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56
         52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e'''
server_private_key = a2b_hex(server_private_key.replace(" ","").replace("\n",""))

b = curve25519.clamp(decodeScalar25519(server_private_key))
B = curve25519.X25519(b, 9)
server_public_key = B.to_bytes(32,'little')
print("server_public_key:", b2a_hex(server_public_key))
# server_public_key: b'c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f'

{server} construct a ServerHello handshake message:

ServerHelloには、CipherSuiteや生成した公開鍵が含まれます。

# {server}  construct a ServerHello handshake message:
ServerHello = '''02 00 00 56 03 03 a6 af 06 a4 12 18 60
         dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e
         d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88
         76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1
         dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04'''
sh = a2b_hex(ServerHello.replace(" ","").replace("\n",""))
tls13sh = TLS13ServerHello(sh)
tls13sh.show()
print("Cipher Suite:", tls13sh.sprintf("%cipher%"))
# Cipher Suite: TLS_AES_128_GCM_SHA256

sh_keyshare = tls13sh.ext[0]
key_exchange = sh_keyshare.server_share.key_exchange
print("key_exchange:", b2a_hex(key_exchange))
# key_exchange: b'c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f'
tls13sh.show()の出力

###[ TLS 1.3 Handshake - Server Hello ]###
msgtype = server_hello
msglen = 86
version = TLS 1.2
random_bytes= a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e26928
sidlen = 0
sid = ''
cipher = TLS_AES_128_GCM_SHA256
comp = null
extlen = 46
\ext
|###[ TLS Extension - Key Share (for ServerHello) ]###
| type = key_share
| len = 36
| \server_share
| |###[ Key Share Entry ]###
| | group = x25519
| | kxlen = 32
| | key_exchange= '\xc9\x82\x88v\x11 \x95\xfefv+\xdb\xf7\xc6r\xe1V\xd6\xcc%;\x83=\xf1\xddi\xb1\xb0Nu\x1f\x0f'
|###[ TLS Extension - Supported Versions (for ServerHello) ]###
| type = supported_versions
| len = 2
| version = TLS 1.3

使う暗号が決まったので、諸々のパラメータを設定しておきます。
RFC5116の5.1. AEAD_AES_128_GCMに書いてあります。
わかりづらいですが、GCMで使用するnonceの長さはiv_lengthと同じです(参考)。

# Cipher Suite: TLS_AES_128_GCM_SHA256
# https://datatracker.ietf.org/doc/html/rfc5116#section-5.1
key_length = 16
iv_length = 12
tag_length = 16
hashtype = "sha256"
hash_length = 32

{server} derive secret for handshake "tls13 derived":

Derive-Secretを用いてHandshake Secretの素を生成します。(※ここで求めるのはsecret for handshakeであってhandshake secretではありません。)
段々ややこしくなってきますので、流れを確認しながら進めます。RFC8446 93ページの図の1回目のDerive-Secret(., "derived", "")です。ここの入力はEarly Secret、"derived"、空のバイト列の3つです。

RFC8448のほうには、PRKhashinfoexpandedという値が記載されています。
PRKは、前に求めたEarly Secretの値です。

hashは、Derive-Secretの内部のTranscript-Hashの出力です。ここでは空バイトのハッシュ値に該当するものです(linuxのコマンドでいうと、$ echo -n "" | sha256sum)。この値は、HKDF-Expand-Labelに渡されます。

infoは、Label("tls13 derived")とハッシュ値とその他諸々とを結合したものです。詳細は関数HKDF_Expand_LabelのHkdfLabelを見ていただければと思います。

expandedはDerive-Secretの出力で、ここではHandshake Secretの素が得られます。

# {server}  derive secret for handshake "tls13 derived":
secret_for_hs = Derive_Secret(early_secret, b"derived", b"", hashtype)
print("secret_for_hs:",b2a_hex(secret_for_hs))
# secret_for_hs: b'6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba'

ECDH

サーバは自身の秘密鍵とクライアントの公開鍵を持っているので、secretを生成できます。この出力は次の処理でHKDF-Extractに入力されます。

bA = curve25519.X25519(b,A)
ecdh_shared_secret = bA.to_bytes(32,'little')
print("ecdh_shared_secret:", b2a_hex(ecdh_shared_secret))
# ecdh_shared_secret: b'8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d'

{server} extract secret "handshake":

ECDHで生成したsecretをもとにHandshake Secretを得ます。

# {server}  extract secret "handshake":
handshake_secret = HKDF_Extract(secret_for_hs, ecdh_shared_secret, hashtype)
print("handshake_secret", b2a_hex(handshake_secret))
# handshake_secret b'1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac'

{server} derive secret "tls13 c hs traffic":

クライアントから送信されるfinishedの復号と、HMAC検証に用いる鍵の素を生成します。
(なのでサーバで使用されるのはクライアントのfinishedを受信した後です。が、本記事では省略します。)

RFCに記載されているhashは、ClientHelloとServerHelloを結合したデータのハッシュ値です(Transcript-Hashの出力)。
その他の値の説明は省略します。

# {server}  derive secret "tls13 c hs traffic":
c_hs_traffic = Derive_Secret(handshake_secret, b"c hs traffic", [ch, sh], hashtype)
print("client_handshake_traffic_secret:",b2a_hex(c_hs_traffic))
# client_handshake_traffic_secret: b'b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21'

{server} derive secret "tls13 s hs traffic":

サーバが送信するfinishedのデータを生成するためのHMAC用鍵と、ハンドシェイクの暗号化を行うための鍵の素です。

# {server}  derive secret "tls13 s hs traffic":
s_hs_traffic = Derive_Secret(handshake_secret, b"s hs traffic", [ch, sh], hashtype)
print("server_handshake_traffic_secret:",b2a_hex(s_hs_traffic))
# server_handshake_traffic_secret: b'b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38'

{server} derive secret for master "tls13 derived":

Master Secretの素を導出します。

# {server}  derive secret for master "tls13 derived":
secret_for_master = Derive_Secret(handshake_secret, b"derived", b"", hashtype)
print("secret_for_master:", b2a_hex(secret_for_master))
# secret_for_master: b'43de77e0c77713859a944db9db2590b53190a65b3ee2e4f12dd7a0bb7ce254b4'

{server} extract secret "master":

Master Secretを導出します。
RFC8446 93ページの図の真ん中あたりまできました。

# {server}  extract secret "master":
IKM = b'\x00' * 32
master_secret = HKDF_Extract(secret_for_master, IKM, hashtype)
print("master_secret:", b2a_hex(master_secret))
# master_secret: b'18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919'

{server} send handshake record:

payloadは、ServerHelloです。
complete recordは、payloadにRecord Layerのヘッダが付与されたものです。

Record Layerの構造はここここに記載されています。
(今更ですが、ClientHelloやServerHelloのフォーマットも記載されています。)

Scapyでも内容を確認できます。

# {server}  send handshake record:
s_hs_rec_header = b'\x16' + b'\x03\x03' + len(sh).to_bytes(2,'big')
s_hs_record = s_hs_rec_header + sh
TLS(s_hs_record).show()
# 出力は省略

{server} derive write traffic keys for handshake data:

やっと共通鍵の生成までたどり着きました。IVは、GCMで必要なnonceの生成に利用します。
これらはCertificateなどの暗号化で使用します。
参考: 7.3. Traffic Key Calculation

# {server}  derive write traffic keys for handshake data:
s_write_key_hs = HKDF_Expand_Label(s_hs_traffic, b"key", b"", key_length, hashtype)
print("write traffic keys for handshake:", b2a_hex(s_write_key_hs))
# write traffic keys for handshake: b'3fce516009c21727d0f2e4e86ee403bc'

s_write_iv_hs = HKDF_Expand_Label(s_hs_traffic, b"iv", b"", iv_length, hashtype)
print("write traffic iv for handshake:", b2a_hex(s_write_iv_hs))
# write traffic iv for handshake: b'5d313eb2671276ee13000b30'

{server} construct an EncryptedExtensions handshake message:

ServerHelloの直後に送信されるデータです。
参考: 4.3.1. Encrypted Extensions

# {server}  construct an EncryptedExtensions handshake message:
EncryptedExtensions = '''08 00 00 24 00 22 00 0a 00 14 00
         12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c
         00 02 40 01 00 00 00 00'''
enc_ext = a2b_hex(EncryptedExtensions.replace(" ","").replace("\n",""))
TLSEncryptedExtensions(enc_ext).show()
TLSEncryptedExtensions(enc_ext).show()の出力

###[ TLS 1.3 Handshake - Encrypted Extensions ]###
msgtype = encrypted_extensions
msglen = 36
extlen = 34
\ext
|###[ TLS Extension - Supported Groups ]###
| type = supported_groups
| len = 20
| groupslen = 18
| groups = [x25519, secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
|###[ TLS Extension - Record Size Limit ]###
| type = record_size_limit
| len = 2
| record_size_limit= 16385
|###[ TLS Extension - Server Name ]###
| type = server_name
| len = 0
| servernameslen= None
| servernames= [b'']

{server} construct a Certificate handshake message:

本記事では中身について触れません。
参考: 4.4.2. Certificate

# {server}  construct a Certificate handshake message:
Certificate = '''0b 00 01 b9 00 00 01 b5 00 01 b0 30 82
         01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48
         86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03
         72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17
         0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06
         03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7
         0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f
         82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26
         d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c
         1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52
         4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74
         80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93
         ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03
         01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06
         03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01
         01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a
         72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea
         e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01
         51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be
         c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b
         1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8
         96 12 29 ac 91 87 b4 2b 4d e1 00 00'''
Certificate = a2b_hex(Certificate.replace(" ","").replace("\n",""))

tls13cert = TLS13Certificate(Certificate)
tls13cert.show()
print("---")
cert = tls13cert[1][0].cert[1]
cert.show()
出力

###[ TLS 1.3 Handshake - Certificate ]###
msgtype = certificate
msglen = 441
cert_req_ctxt_len= 0
cert_req_ctxt= ''
certslen = 437
\certs
|###[ Certificate and Extensions ]###
| cert = (432, [X.509 Cert. Subject:/CN=rsa, Issuer:/CN=rsa])
| extlen = 0
| \ext \


Serial: 2
Issuer: /CN=rsa
Subject: /CN=rsa
Validity: Jul 30 01:23:59 2016 GMT to Jul 30 01:23:59 2026 GMT

{server} construct a CertificateVerify handshake message:

参考: 4.4.3. Certificate Verify

# {server}  construct a CertificateVerify handshake message:
CertificateVerify = '''0f 00 00 84 08 04 00 80 5a 74 7c
         5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a
         b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07
         86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b
         be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44
         5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a
         3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3'''
CertificateVerify = a2b_hex(CertificateVerify.replace(" ","").replace("\n",""))

TLSCertificateVerify(CertificateVerify).show()
TLSCertificateVerify(CertificateVerify).show()の出力

###[ TLS Handshake - Certificate Verify ]###
msgtype = certificate_verify
msglen = 132
\sig
|###[ TLS Digital Signature ]###
| sig_alg = sha256+rsaepss
| sig_len = 128
| sig_val = "Zt|]\x88\xfa\x9b\xd2\xe5Z\xb0\x85\xa6\x10\x15\xb7!\x1f\x82L\xd4\x84\x14Z\xb3\xffR\xf1\xfd\xa8G{\x0bz\xbc\x90\xdbx\xe2\xd3:\\x14\x1a\x07\x86S\xfak\xefx\x0c^\xa2H\xee\xaa\xa7\x85\xc4\xf3\x94\xca\xb6\xd3\x0b\xbe\x8dHY\xeeQ\x1f`)W\xb1T\x11\xac\x02vqE\x9eFD\\x9e\xa5\x8c\x18\x1e\x81\x8e\x95\xb8\xc3\xfb\x0b\xf3'\x84\t\xd3\xbe\x15*=\xa5\x04>\x06=\xdae\xcd\xf5\xae\xa2\rS\xdf\xac\xd4/t\xf3"

{server} calculate finished "tls13 finished":

HMACを使用してfinishedのデータを生成します。
鍵はHKDF_Expand_Labelで導出します。
入力するデータは、ClientHello、ServerHello、EncryptedExtensions、Certificate、CertificateVerifyを連結したデータのハッシュ値です(要するにTranscript_Hashの出力)。

参考: 4.4.4. Finished

# {server}  calculate finished "tls13 finished":
BaseKey = s_hs_traffic
s_finished_key = HKDF_Expand_Label(BaseKey, b"finished", b"", hash_length, hashtype)
print("server finished_key:", b2a_hex(s_finished_key))
# server finished_key: b'008d3b66f816ea559f96b537e885c31fc068bf492c652f01f288a1d8cdc19fc8'

s_verify_data = hmac.new(s_finished_key, \
                       Transcript_Hash([ch, sh, enc_ext, Certificate, CertificateVerify], hashtype), \
                       hashlib.sha256).digest()
print("server verify_data:", b2a_hex(s_verify_data))
# server verify_data: b'9b9b141d906337fbd2cbdce71df4deda4ab42c309572cb7fffee5454b78f0718'

{server} construct a Finished handshake message:

上で求めた値にヘッダなどを付加します。

# {server}  construct a Finished handshake message:
s_finished = b'\x14' + len(s_verify_data).to_bytes(3,'big') + s_verify_data
print("server finished:", b2a_hex(s_finished))
# server finished: b'140000209b9b141d906337fbd2cbdce71df4deda4ab42c309572cb7fffee5454b78f0718'

{server} send handshake record:

ここで初めての暗号化です。
Encrypted Extention、Certificate、CertificateVerify、finishedを連結したデータ(payload)の暗号化を行います。
GCMに必要であるnonceの求め方は、5.3. Per-Record Nonceのとおりです。

※ payloadの最後に 謎の ContentTypeがhandshakeであることを示す1バイト(0x16)が付きます。 これが何かご存じの方、教えていただけると嬉しいです。 (satokenさんから教えていただきました。ありがとうございます。)

# {server}  send handshake record:
payload = enc_ext + Certificate + CertificateVerify + s_finished
hs_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = hs_record_header

# https://datatracker.ietf.org/doc/html/rfc8446#section-5.3
seq_num = 0
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, s_write_iv_hs)])

cipher = AES.new(s_write_key_hs, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x16') # ContentType handshake

AEADEncrypted = ciphertext + tag
hs_record  = hs_record_header + AEADEncrypted
print("hs_record:", b2a_hex(hs_record))
# hs_record: b'17030302a2d1ff334a56f5bff6594a07cc87b580233f500f45e489e7f33af35edf7869fcf40aa40aa2b8ea73f848a7ca07612ef9f945cb960b4068905123ea78b111b429ba9191cd05d2a389280f526134aadc7fc78c4b729df828b5ecf7b13bd9aefb0e57f271585b8ea9bb355c7c79020716cfb9b1183ef3ab20e37d57a6b9d7477609aee6e122a4cf51427325250c7d0e509289444c9b3a648f1d71035d2ed65b0e3cdd0cbae8bf2d0b227812cbb360987255cc744110c453baa4fcd610928d809810e4b7ed1a8fd991f06aa6248204797e36a6a73b70a2559c09ead686945ba246ab66e5edd8044b4c6de3fcf2a89441ac66272fd8fb330ef8190579b3684596c960bd596eea520a56a8d650f563aad27409960dca63d3e688611ea5e22f4415cf9538d51a200c27034272968a264ed6540c84838d89f72c24461aad6d26f59ecaba9acbbb317b66d902f4f292a36ac1b639c637ce343117b659622245317b49eeda0c6258f100d7d961ffb138647e92ea330faeea6dfa31c7a84dc3bd7e1b7a6c7178af36879018e3f252107f243d243dc7339d5684c8b0378bf30244da8c87c843f5e56eb4c5e8280a2b48052cf93b16499a66db7cca71e4599426f7d461e66f99882bd89fc50800becca62d6c74116dbd2972fda1fa80f85df881edbe5a37668936b335583b599186dc5c6918a396fa48a181d6b6fa4f9d62d513afbb992f2b992f67f8afe67f76913fa388cb5630c8ca01e0c65d11c66a1e2ac4c85977b7c7a6999bbf10dc35ae69f5515614636c0b9b68c19ed2e31c0b3b66763038ebba42f3b38edc0399f3a9f23faa63978c317fc9fa66a73f60f0504de93b5b845e275592c12335ee340bbc4fddd502784016e4b3be7ef04dda49f4b440a30cb5d2af939828fd4ae3794e44f94df5a631ede42c1719bfdabf0253fe5175be898e750edc53370d2b'

{server} derive secret "tls13 c ap traffic":

クライアントから送信されたapplication dataを復号するための鍵の素を導出します。

# {server}  derive secret "tls13 c ap traffic":
msgs = [ch, sh, enc_ext, Certificate, CertificateVerify, s_finished]
c_ap_traffic = Derive_Secret(master_secret, b"c ap traffic", msgs, hashtype)
print("client_application_traffic_secret_0:", b2a_hex(c_ap_traffic))
# client_application_traffic_secret_0: b'9e40646ce79a7f9dc05af8889bce6552875afa0b06df0087f792ebb7c17504a5'

{server} derive secret "tls13 s ap traffic":

サーバがapplication dataの暗号化を行うための鍵の素を導出します。

# {server}  derive secret "tls13 s ap traffic":
msgs = [ch, sh, enc_ext, Certificate, CertificateVerify, s_finished]
s_ap_traffic = Derive_Secret(master_secret, b"s ap traffic", msgs, hashtype)
print("server_application_traffic_secret_0:", b2a_hex(s_ap_traffic))
# server_application_traffic_secret_0: b'a11af9f05531f856ad47116b45a950328204b4f44bfb6b3a4b4f1f3fcb631643'

{server} derive secret "tls13 exp master":

7.5. Exportersに登場します(わかってない)。

# {server}  derive secret "tls13 exp master":
msgs = [ch, sh, enc_ext, Certificate, CertificateVerify, s_finished]
exporter_master_secret = Derive_Secret(master_secret, b"exp master", msgs, hashtype)
print("exporter_master_secret:", b2a_hex(exporter_master_secret))
# exporter_master_secret: b'fe22f881176eda18eb8f44529e6792c50c9a3f89452f68d8ae311b4309d3cf50'

{server} derive write traffic keys for application data:

サーバのapplication data用暗号鍵です。

# {server}  derive write traffic keys for application data:
s_write_key_ap = HKDF_Expand_Label(s_ap_traffic, b"key", b"", key_length, hashtype)
print("write traffic keys for application data:", b2a_hex(s_write_key_ap))
# write traffic keys for application data: b'9f02283b6c9c07efc26bb9f2ac92e356'

s_write_iv_ap = HKDF_Expand_Label(s_ap_traffic, b"iv", b"", iv_length, hashtype)
print("write traffic iv for application data:", b2a_hex(s_write_iv_ap))
# write traffic iv for application data: b'cf782b88dd83549aadf1e984'

{server} derive read traffic keys for handshake data:

クライアントから送信されたハンドシェイクのデータを復号するための鍵です。

# {server}  derive read traffic keys for handshake data:
c_write_key_hs = HKDF_Expand_Label(c_hs_traffic, b"key", b"", key_length, hashtype)
print("write traffic keys for handshake:", b2a_hex(c_write_key_hs))
# write traffic keys for handshake: b'dbfaa693d1762c5b666af5d950258d01'

c_write_iv_hs = HKDF_Expand_Label(c_hs_traffic, b"iv", b"", iv_length, hashtype)
print("write traffic iv for handshake:", b2a_hex(c_write_iv_hs))
# write traffic iv for handshake: b'5bd3c71b836e0b76bb73265f'

クライアント側処理②

ここから再びクライアントの処理に戻ります。
しかし共通鍵の導出などはサーバでの処理と同様なのでその辺は省略します。
RFC8448でもsame as serverと記されています。

ECDH

RFC8448では省略されていますが、クライアントでもECDHの計算を行います。
当然ですがサーバで生成した値と同じになります。

aB = curve25519.X25519(a,B)
assert(aB == bA)
ecdh_shared_secret = aB.to_bytes(32,'little')
print("ecdh_shared_secret:", b2a_hex(ecdh_shared_secret))
# ecdh_shared_secret: b'8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d'

{client} calculate finished "tls13 finished":

HMACを使って、クライアントが送信するFinished Messageを生成します。

# {client}  calculate finished "tls13 finished":
c_finished_key = HKDF_Expand_Label(c_hs_traffic, b"finished", b"", hash_length, hashtype)
print("finished_key:", b2a_hex(c_finished_key))
# finished_key: b'b80ad01015fb2f0bd65ff7d4da5d6bf83f84821d1f87fdc7d3c75b5a7b42d9c4'

msgs = [ch, sh, enc_ext, Certificate, CertificateVerify, s_finished]
c_verify_data = hmac.new(c_finished_key, \
                       Transcript_Hash(msgs, hashtype), \
                       hashlib.sha256).digest()
print("client verify_data:", b2a_hex(c_verify_data))
# client verify_data: b'a8ec436d677634ae525ac1fcebe11a039ec17694fac6e98527b642f2edd5ce61'

{client} construct a Finished handshake message:

Finishedのデータにヘッダを付加します。

# {client}  construct a Finished handshake message:
c_finished = b'\x14' + len(c_verify_data).to_bytes(3,'big') + c_verify_data
print("client finished:", b2a_hex(c_finished))
# client finished: b'14000020a8ec436d677634ae525ac1fcebe11a039ec17694fac6e98527b642f2edd5ce61'

{client} send handshake record:

Finished Messageを暗号化します。(ここでもpayloadの最後に 謎の ContentTypeを示す1バイトがつく)
主要?なハンドシェイクの通信はこれで以上です。

# {client}  send handshake record:
payload = c_finished
hs_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = hs_record_header

seq_num = 0
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, c_write_iv_hs)])

cipher = AES.new(c_write_key_hs, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x16') # ContentType handshake

AEADEncrypted = ciphertext + tag
hs_record  = hs_record_header + AEADEncrypted
print("hs_record:", b2a_hex(hs_record))
# hs_record: b'170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546'

{client} derive write traffic keys for application data:

クライアントがapplication dataを暗号化するための鍵、IVを生成します。

# {client}  derive write traffic keys for application data:
c_write_key_ap = HKDF_Expand_Label(c_ap_traffic, b"key", b"", key_length, hashtype)
print("write traffic keys for application data:", b2a_hex(c_write_key_ap))
# write traffic keys for application data: b'17422dda596ed5d9acd890e3c63f5051'

c_write_iv_ap = HKDF_Expand_Label(c_ap_traffic, b"iv", b"", iv_length, hashtype)
print("write traffic iv for application data:", b2a_hex(c_write_iv_ap))
# write traffic iv for application data: b'5b78923dee08579033e523d9'

{client} derive secret "tls13 res master":

セッションの再開に使用されるらしいです。(あんまりわかってないです)

# {client}  derive secret "tls13 res master":
msgs = [ch, sh, enc_ext, Certificate, CertificateVerify, s_finished, c_finished]
resumption_master_secret = Derive_Secret(master_secret, b"res master", msgs, hashtype)
print("resumption_master_secret:", b2a_hex(resumption_master_secret))
# resumption_master_secret: b'7df235f2031d2a051287d02b0241b0bfdaf86cc856231f2d5aba46c434ec196c'

サーバ側処理②

4.6. Post-Handshake Messagesというものがあるそうです。

{server} generate resumption secret "tls13 resumption":

参考: 4.6.1. New Session Ticket Message

# {server}  generate resumption secret "tls13 resumption":
resumption_secret = HKDF_Expand_Label(resumption_master_secret, b"resumption", b'\x00\x00', hash_length, hashtype)
print("resumption_secret:", b2a_hex(resumption_secret))
# resumption_secret: b'4ecd0eb6ec3b4d87f5d6028f922ca4c5851a277fd41311c9e62d2c9492e1c4f3'

{server} construct a NewSessionTicket handshake message:

# {server}  construct a NewSessionTicket handshake message:
NewSessionTicket = '''04 00 00 c9 00 00 00 1e fa d6 aa
         c5 02 00 00 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00 00 00
         00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70 ad 3c
         49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9 82 11
         72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6 1d 28
         27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0 37 25
         a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5 90 6c
         5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5 ae a6
         17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d e6 50
         5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 00 08 00 2a 00
         04 00 00 04 00'''
NewSessionTicket = a2b_hex(NewSessionTicket.replace(" ","").replace("\n",""))
TLS13NewSessionTicket(NewSessionTicket).show()
TLS13NewSessionTicket(NewSessionTicket).show()の出力

###[ TLS 1.3 Handshake - New Session Ticket ]###
msgtype = session_ticket
msglen = 201
ticket_lifetime= 30
ticket_age_add= 4208372421
noncelen = 2
ticket_nonce= '\x00\x00'
ticketlen = 178
ticket = ',\x03]\x82\x93Y\xee_\xf7\xafN\xc9\x00\x00\x00\x00&d\x94\xdcHm,\x8a4\xcb3\xfa\x90\xbf\x1b\x00p\xad<I\x88\x83\xc96|\t\xa2\xbexZ\xbcU\xcd"`\x97\xa3\xa9\x82\x11r\x83\xf8\x03\xa1C\xef\xd3\xff]\xd3md\xe8a\xbe\x7f\xd6\x1d('\xdb'\x9c\xce\x14Pw\xd4T\xa3fMNm\xa4\xd2\x9e\xe07%\xa6\xa4\xda\xfc\xd0\xfcg\xd2\xae\xa7\x05)Q>=\xa2g\x7f\xa5\x90l[?}\x8f\x92\xf2(\xbd\xa4\r\xdar\x14p\xf9\xfb\xf2\x97\xb5\xae\xa6\x17do\xac\\x03'.\x97\x07'\xc6!\xa7\x91A\xef_}\xe6P^[\xfb\xc3\x88\xe93Ci@\x93\x93J\xe4\xd3W'
extlen = 8
\ext
|###[ TLS Extension - Ticket Early Data Info ]###
| type = early_data_indication
| len = 4
| max_early_data_size= 1024

{server} send handshake record:

NewSessionTicketの暗号化を行います。ここで使う鍵はapplication data用のものです。
やはり、payloadの最後に 謎の ContentTypeを示す1バイトがつきます。

# {server}  send handshake record:
payload = NewSessionTicket
hs_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = hs_record_header

seq_num = 0
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, s_write_iv_ap)])

cipher = AES.new(s_write_key_ap, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x16') # ContentType handshake

AEADEncrypted = ciphertext + tag
hs_record  = hs_record_header + AEADEncrypted
print("hs_record:", b2a_hex(hs_record))
# hs_record: b'17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f'

application dataの送信

もう終盤です。ここではTLSの範囲外、上の層のデータを暗号化します。

{client} send application_data record:

中身のデータは適当ですね。
ここではpayloadの最後に ContentType がapplication_data であることを示す0x17がつく らしい

# {client}  send application_data record:
payload = '''00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e
         0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23
         24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31'''
payload = a2b_hex(payload.replace(" ","").replace("\n",""))

ap_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = ap_record_header

seq_num = 0
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, c_write_iv_ap)])

cipher = AES.new(c_write_key_ap, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x17') # ContentType application_data

AEADEncrypted = ciphertext + tag
ap_record  = ap_record_header + AEADEncrypted
print("ap_record:", b2a_hex(ap_record))
# ap_record: b'1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4'

{server} send application_data record:

サーバ側。同上。

# {server}  send application_data record:
payload = '''00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e
         0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23
         24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31'''
payload = a2b_hex(payload.replace(" ","").replace("\n",""))

ap_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = ap_record_header

seq_num = 1
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, s_write_iv_ap)])

cipher = AES.new(s_write_key_ap, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x17') # ContentType application_data

AEADEncrypted = ciphertext + tag
ap_record  = ap_record_header + AEADEncrypted
print("ap_record:", b2a_hex(ap_record))
# ap_record: b'17030300432e937e11ef4ac740e538ad36005fc4a46932fc3225d05f82aa1b36e30efaf97d90e6dffc602dcb501a59a8fcc49c4bf2e5f0a21c0047c2abf332540dd032e167c2955d'

Alert Protocol

6. Alert Protocolでは、通信の終了やエラーの発生を知らせます。これも暗号化を行います。

{client} send alert record:

# {client}  send alert record:
payload = '01 00'
payload = a2b_hex(payload.replace(" ","").replace("\n",""))

ap_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = ap_record_header

seq_num = 1
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, c_write_iv_ap)])

cipher = AES.new(c_write_key_ap, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x15') # ContentType alert

AEADEncrypted = ciphertext + tag
ap_record  = ap_record_header + AEADEncrypted
print("ap_record:", b2a_hex(ap_record))
# ap_record: b'1703030013c9872760655666b74d7ff1153efd6db6d0b0e3'

{server} send alert record:

#  {server}  send alert record:
payload = '01 00'
payload = a2b_hex(payload.replace(" ","").replace("\n",""))

ap_record_header = b'\x17' + b'\x03\x03' + (len(payload)+1+tag_length).to_bytes(2,'big')
additional_data = ap_record_header

seq_num = 2
seq_num = a2b_hex(format(seq_num,'016x')) # 64-bit
padded_seq_num = b'\x00' * (iv_length - len(seq_num)) + seq_num
nonce = bytes([a^b for a,b in zip(padded_seq_num, s_write_iv_ap)])

cipher = AES.new(s_write_key_ap, AES.MODE_GCM, nonce)
cipher.update(additional_data)
ciphertext,tag = cipher.encrypt_and_digest(payload + b'\x15') # ContentType alert

AEADEncrypted = ciphertext + tag
ap_record  = ap_record_header + AEADEncrypted
print("ap_record:", b2a_hex(ap_record))
# ap_record: b'1703030013b58fd67166ebf599d24720cfbe7efa7a8864a9'

おしまい

勉強になりました。

GitHubで編集を提案

Discussion

satokensatoken

※ payloadの最後に謎の1バイト(0x16)が付きます。これが何かご存じの方、教えていただけると嬉しいです。

ContentTypeがHandshakeであることを表す16ですね。
5.2. Record Payload Protectionに記載されている以下の説明です。

TLSCiphertextレコードの外側のopaque_typeフィールドは、以前のバージョンのTLSの解析に慣れているミド > ルボックスとの外部互換性のために、常に値23(application_data)に設定されます。 レコードの実際のコン> テンツタイプは、復号化後にTLSInnerPlaintext.typeで見つかります。

0a240a24

教えていただきありがとうございます。
TLSInnerPlaintext.typeの値ですね。見落としておりました。