0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TLS1.3の暗号化の仕組み

0
Posted at

TLS1.2とTLS1.3のプロトコルについて」で TLS1.2TLS1.3 の違いについて書きました。今回は TLS1.3 でどのように暗号化が行われているかを少し詳細に書いてみました。

1. TLS1.3のシーケンス

2. 暗号化の例

2.1. ClientHello(クライアント → サーバ)

ClientHello
  random = C_RANDOM

  cipher_suites =
    TLS_AES_128_GCM_SHA256
    TLS_AES_256_GCM_SHA384

  supported_groups =
    X25519, secp256r1

  key_share =
    group: X25519
    public_key: C_pub

  signature_algorithms =
    rsa_pss_rsae_sha256
    ecdsa_secp256r1_sha256
  • random
    • クライアントで生成した32バイトの乱数を設定
  • cipher_suites
    • クライアントが使用できる暗号スイートの一覧
      • TLS_AES_128_GCM_SHA256
        • 通信の暗号アルゴリズム → AES_128_GCM
        • HKDF(鍵導出関数)で使うハッシュアルゴリズム → SHA256
      • TLS_AES_256_GCM_SHA384
        • 通信の暗号アルゴリズム → AES_256_GCM
        • HKDF(鍵導出関数)で使うハッシュアルゴリズム → SHA384
  • supported_groups
    • 鍵交換で使用するグループ(楕円曲線や有限体)の候補
      • X25519, secp256r1
        • 楕円曲線ディフィー・ヘルマン(ECDHE)で使う「曲線の種類」

TLS 1.2では、鍵交換アルゴリズムとしてRSA, DH, DHE, ECDH, ECDHEが使用できましたが、TLS1.3ではDHE, ECDHEとなっています。
TLS 1.3のフルハンドシェイクでは、前方秘匿性(PFS)を確保するために、(EC)DHE(またはそれとポスト量子暗号を組み合わせたハイブリッド方式)による鍵共有が必須となっています。

  • key_share
    • 鍵交換で使用するグループ(楕円曲線や有限体)とその公開鍵
      • group → 鍵交換で使用するグループを指定
      • public_key → クライアントの公開鍵を指定

サーバがクライアントの最初のkey_shareグループに対応していなかった場合のみ、サーバは HelloRetryRequest を返します。
(例: X25519 未対応だが secp256r1 は対応している場合)

HelloRetryRequest
  group: secp256r1

HelloRetryRequestを受け取ったクライアントはClientHelloを再送します。

ClientHello(再送)
  key_share =
    group: secp256r1
    public_key: 新しい C_pub
  • signature_algorithms
    • サーバが署名で使ってよいアルゴリズム一覧
      • rsa_pss_rsae_sha256
        • rsa_pss:署名方式(RSA-PSS署名方式)
        • rsae:鍵の種類(RSA公開鍵)
        • sha256:ハッシュアルゴリズム
      • ecdsa_secp256r1_sha256
        • ecdsa:署名方式(楕円曲線署名)
        • secp256r1:鍵の種類(使用する曲線)
        • sha256:ハッシュアルゴリズム

この時点でクライアントとサーバは初期シークレット(early_secret)を生成します。
TLS1.3では、PSK(Pre-Shared Key) がない場合は0で初期値を設定します。

early_secret = HKDF-Extract(0, PSK=0)

PRK = HKDF-Extract(salt, input_key_material)
salt : 前段のシークレット(鍵導出の基準となる値)
input : 新しく取り込むシークレット

2.2. ServerHello(サーバ → クライアント)

ServerHello
  random = S_RANDOM

  cipher_suite =
    TLS_AES_128_GCM_SHA256

  key_share =
    group: X25519
    public_key: S_pub
  • random
    • サーバで生成した32バイトの乱数を設定
  • cipher_suite
    • 使用する暗号スイートを決定
      • クライアントに提示された候補から選択しTLS_AES_128_GCM_SHA256に決定
  • key_share
    • 鍵交換で使用するグループ(楕円曲線や有限体)とその公開鍵
      • group → 鍵交換で使用するグループを指定
      • public_key → サーバの公開鍵を指定

TLS1.3 では HelloRetryRequest は ServerHello と同じ構造を持つ特殊メッセージです。
サーバが対応可能なグループを通知し、クライアントはそのグループで ClientHello を再送します。
例:

HelloRetryRequest
  group: secp256r1
ClientHello(再送)
  key_share =
    group: secp256r1
    public_key: 新しい C_pub

この時点で、暗号スイート鍵交換で使用するグループ及びその公開鍵が決まります。

  • 暗号スイート : AES_128_GCM + SHA256
  • 鍵交換 : X25519
  • 公開鍵 : C_pub、S_pub

ここで、クライアントとサーバそれぞれで、

  • 共有鍵(shared_secret)
  • ハンドシェイクシークレット(handshake_secret)
  • ハンドシェイク鍵(client_handshake_traffic_secret、server_handshake_traffic_secret)

を生成します。

2.2.1. 共有鍵(shared_secret)の生成

クライアント

shared_secret = X25519(C_priv, S_pub)
  • C_priv : クライアントの秘密鍵
  • S_pub : サーバの公開鍵

サーバ

shared_secret = X25519(S_priv, C_pub)
  • S_priv : サーバの秘密鍵
  • C_pub : クライアントの公開鍵

2.2.2. ハンドシェイクシークレット(handshake_secret)の生成

クライアント及びサーバ

handshake_secret = HKDF-Extract(early_secret, shared_secret)
  • early_secret : 初期シークレット
  • shared_secret : 共有鍵

2.2.3. ハンドシェイク鍵(client_handshake_traffic_secret、server_handshake_traffic_secret)の生成

クライアント及びサーバ

client_handshake_traffic_secret = HKDF-Expand(handshake_secret, "c hs traffic", L)
  • handshake_secret : ハンドシェイクシークレット
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32
server_handshake_traffic_secret = HKDF-Expand(handshake_secret, "s hs traffic", L)
  • handshake_secret : ハンドシェイクシークレット
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

OKM = HKDF-Expand(PRK, info, L)

  • OKM : 出力鍵材料
  • PRK : 前段のシークレット(鍵導出の基準となる値)
  • info : 鍵の用途やラベルなどの追加情報(導出する鍵を区別するため)
  • L : 生成したい鍵の長さ(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

2.2.4. ハンドシェイク用の AES鍵と IV の生成

クライアント及びサーバ

client_handshake_write_key = HKDF-Expand(client_handshake_traffic_secret, "key", L)
client_handshake_write_IV  = HKDF-Expand(client_handshake_traffic_secret, "iv", 12)

server_handshake_write_key = HKDF-Expand(server_handshake_traffic_secret, "key", L)
server_handshake_write_IV  = HKDF-Expand(server_handshake_traffic_secret, "iv", 12)
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32
  • client_handshake_write_key : クライアントが「送信データを暗号化」、サーバが「受信データを復号」するために使用
  • client_handshake_write_IV : クライアントが送信データ暗号化で AES-GCM の初期化ベクトルとして使用、サーバは受信データ復号で同じ IV を使用
  • server_handshake_write_key : サーバが「送信データを暗号化」、クライアントが「受信データを復号」するために使用
  • server_handshake_write_IV : サーバが送信データ暗号化で AES-GCM の初期化ベクトルとして使用、クライアントは受信データ復号で同じ IV を使用

ハンドシェイク用の AES鍵と IV は EncryptedExtensions, Certificate, CertificateVerify, Finished の暗号化/復号に使用されます。

2.3. EncryptedExtensions(サーバ → クライアント)

EncryptedExtensions
  ALPN: http/1.1
  • 暗号化
    • 鍵 → server_handshake_traffic_secret
    • アルゴリズム → AES_128_GCM

2.4. Certificate(サーバ → クライアント)

Certificate
  サーバ証明書(RSA公開鍵)

クライアントはCA公開鍵でサーバ証明書の署名検証を行い、サーバ証明書が本物であることを検証します。

※サーバ証明書の確認については「TLSにおけるブラウザの証明書のチェック」を参照

2.5. CertificateVerify(サーバ → クライアント)

CertificateVerify
  algorithm = ecdsa_secp256r1_sha256
  signature = Sign(handshake_messages, サーバ秘密鍵)
  • algorithm : 署名アルゴリズム(ClientHello の signature_algorithms の候補からサーバが選択)
  • handshake_messages : ハンドシェイク中に送受信したメッセージをすべて連結したもの
  • signature : handshake_messages のハッシュ値(選択したハッシュアルゴリズム「ここではecdsa_secp256r1_sha256」を使用)をサーバ秘密鍵で署名

 

クライアントで署名を検証します。
検証がOKであれば、サーバが秘密鍵を持っていることが証明されます。

Verify(signature, サーバ公開鍵)
  • handshake_messages のハッシュ値(サーバが指定したハッシュアルゴリズム「ここではecdsa_secp256r1_sha256」を使用)を求める
  • signature をサーバの公開鍵で検証(handshake_messages のハッシュ値の一致を確認)

2.6. Finished(サーバ → クライアント)

Finished
  verify_data

verify_data は以下で求めます。

verify_data =
  HMAC(
    finished_key,
    Hash(handshake_messages)
  )
  • finished_key : HMAC の key(HKDF-Expand(handshake_secret, "finished", L) で導出)
  • Hash(handshake_messages) : HMAC の message(ハンドシェイクでやり取りした全メッセージのハッシュ)

なお、finished_key は以下で生成します。

finished_key =
  HKDF-Expand(server_handshake_traffic_secret, "finished", L)
  • server_handshake_traffic_secret : ハンドシェイクシークレット
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

出力 = HMAC(key, message)

  • key : 認証のための秘密鍵(固定長のバイト列)
  • message : 認証対象のデータ
  • 出力 : 認証コード(MAC値、改ざん検出用)

HMAC(Hash-based Message Authentication Code)は ハッシュ関数を用いたメッセージ認証コード です。

クライアント側では同じ計算をして一致確認します。

finished_key = HKDF-Expand(server_handshake_traffic_secret, "finished", L)
  • server_handshake_traffic_secret : ハンドシェイクシークレット(server_handshake_traffic_secretを使用する)
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32
verify_data_local = HMAC(finished_key, Hash(handshake_messages))
  • finished_key : HMAC の key(HKDF-Expand(server_handshake_traffic_secret, "finished", L) で導出)
  • Hash(handshake_messages) : HMAC の message(ハンドシェイクでやり取りした全メッセージのハッシュ)

verify_data と verify_data_local が一致することを確認します。

2.7. Finished(クライアント → サーバ)

Finished
  verify_data = verify_data_client

verify_data_client は以下で求めます。

verify_data_client =
  HMAC(
    finished_key,
    Hash(handshake_messages)
  )
  • finished_key : HMAC の key(HKDF-Expand(handshake_secret, "finished", L) で導出)
  • Hash(handshake_messages) : HMAC の message(ハンドシェイクでやり取りした全メッセージのハッシュ)

なお、finished_key は以下で生成します(AES-GCM 暗号化とは独立に HMAC 用の鍵として導出されます)。

finished_key =
  HKDF-Expand(client_handshake_traffic_secret, "finished", L)
  • client_handshake_traffic_secret : ハンドシェイクシークレット
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

 

サーバ側では同じ計算をして一致確認します。

finished_key = HKDF-Expand(client_handshake_traffic_secret, "finished", L)
  • client_handshake_traffic_secret : ハンドシェイクシークレット(client_handshake_traffic_secret を使用する)
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32
verify_data_server = HMAC(finished_key, Hash(handshake_messages))
  • finished_key : HMAC の key(HKDF-Expand(client_handshake_traffic_secret, "finished", L) で導出)
  • Hash(handshake_messages) : HMAC の message(ハンドシェイクでやり取りした全メッセージのハッシュ)

verify_data(クライアント) と verify_data_server が一致することを確認します。

アプリケーション鍵の生成

クライアント及びサーバでアプリケーション鍵を生成します。

master_secret生成

master_secret = HKDF-ExpandLabel(handshake_secret, "master secret", "", L)
  • handshake_secret : ハンドシェイクシークレット
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

OKM = HKDF-ExpandLabel(Secret, Label, Context, L)

  • Secret : 元のシークレット(PRK に相当)
  • Label : 用途を示す文字列(例: "master secret", "c ap traffic", "key", "iv")
  • Context: 任意の追加情報(通常は空文字列)
  • L : 生成する鍵の長さ(バイト単位)

アプリ鍵生成

client_application_traffic_secret = HKDF-Expand(master_secret, "c ap traffic", L)

server_application_traffic_secret = HKDF-Expand(master_secret, "s ap traffic", L)
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32

AES鍵とIV(初期ベクトル)の生成

client_write_key = HKDF-Expand(client_application_traffic_secret, "key", L)
client_write_IV  = HKDF-Expand(client_application_traffic_secret, "iv", 12)

server_write_key = HKDF-Expand(server_application_traffic_secret, "key", L)
server_write_IV  = HKDF-Expand(server_application_traffic_secret, "iv", 12)
  • L : 暗号アルゴリズムに依存する鍵長(バイト単位)
    • AES-128 の場合 → 16
    • AES-256 の場合 → 32
  • client_write_key : クライアントが「送信データを暗号化」、サーバが「受信データを復号」するために使用
  • client_write_IV : クライアントが送信データ暗号化で AES-GCM の初期化ベクトルとして使用、サーバは受信データ復号で同じ IV を使用
  • server_write_key : サーバが「送信データを暗号化」、クライアントが「受信データを復号」するために使用
  • server_write_IV : サーバが送信データ暗号化で AES-GCM の初期化ベクトルとして使用、クライアントは受信データ復号で同じ IV を使用

2.8. アプリケーション通信

2.8.1. クライアントからの送信例

平文

GET / HTTP/1.1
Host: example.com

暗号化

  • クライアントは TLS レコードを作成
    • タイプ: Application Data
    • バージョン: TLS 1.3
    • ペイロード: 上記 HTTP メッセージ

AES-GCM で暗号化

nonce = client_write_IV XOR sequence_number
ciphertext = AES-128-GCM-Encrypt(client_write_key, nonce, plaintext, AAD)
  • client_write_IV : 初期化ベクトル(方向ごとに異なる)
  • sequence_number : TLS レコードの順序番号
  • client_write_key : クライアント送信鍵
  • plaintext : 平文
  • AAD : TLS レコードヘッダの認証用データ
  • 出力は 暗号文 + 認証タグ

TLS1.3 では AES-GCM の認証タグで改ざん検知が可能なため、別途 HMAC は不要です。

暗号化済みデータを TCP に載せて送信

2.8.2. サーバ側での復号

TCP 受信

TLS レコードからペイロードを取り出す

AES-GCM で復号

nonce = client_write_IV XOR sequence_number
plaintext = AES-128-GCM-Decrypt(client_write_key, nonce, ciphertext, AAD)
  • client_write_key : クライアントが「送信データを暗号化」、サーバが「受信データを復号」するために使用
  • client_write_IV : クライアントが送信データ暗号化で AES-GCM の初期化ベクトルとして使用、サーバは受信データ復号で同じ IV を使用
  • sequence_number : TLS レコードの順序番号
  • ciphertext : 暗号文 + 認証タグ
  • AAD : TLS レコードヘッダの認証用データ
  • 出力は 平文

3. 全体の「暗号の役割」まとめ

フェーズ 使用暗号 役割
ClientHello なし 候補提示、ハンドシェイク開始
ServerHello X25519 鍵交換(ECDHE)
鍵生成 (handshake_secret → handshake traffic keys) HKDF(SHA256) ハンドシェイク鍵導出
EncryptedExtensions AES_128_GCM 暗号化ハンドシェイク(サーバ→クライアント)
Certificate AES_128_GCM 暗号化ハンドシェイク内で送信、身元証明
CertificateVerify AES_128_GCM 暗号化ハンドシェイク内で署名検証、なりすまし防止
Finished AES_128_GCM(認証付き) 暗号化ハンドシェイク、完全性確認(HMAC verify_data による改ざん検知)
Application Data AES_128_GCM 暗号化通信(送受信データの機密性と改ざん検知)

参考


以上

0
0
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?