フューチャー技術ブログ

【実践編】HTTPS通信の中身を見て、どのようにしてセキュアな通信が確立されるかを理解する

こんにちは、CSIG 所属の市川です。普段は FutureVuls という脆弱性管理サービスの開発・カスタマーサポートをしています。

【理論編】HTTPS通信の中身を見て、どのようにしてセキュアな通信が確立されるかを理解するの続編です。

TLSハンドシェイクの中身を順番に見ていくため、理論編で示したシーケンス図をチラ見しながら読んでいただけると理解が進みやすいかと思います。

また、TLSハンドシェイクについて理解を深めたい方は、あわせて理論編もお読みください。

本記事 (実践編) でやりたいこと

理論編では、以下の3点について解説してきました。

  • 各アルゴリズムが何のために使われるかを整理する
  • TLS1.2ハンドシェイクを、シーケンス図を用いて整理する
  • 鍵交換方式について理解を深める

本記事 (実践編) では、「Wireshark」というツールを用いて TLS ハンドシェイクの中身を実際に覗いてみて、理論編の内容を確認していきます。

TLS1.3 だと、ハンドシェイクの序盤で通信が暗号化されるのですが、今回の環境では通信の復号がうまく行えず、ハンドシェイクの中身を平文で確認できませんでした。そのため、本記事では TLS1.2 の通信を扱います。

TLS1.3ハンドシェイクはTLS1.2ハンドシェイクとは大きく異なります。TLS1.3 について理解したい方は、プロフェッショナルTLS&PKI 改題第2版などの書籍などを参考にしてください。

では、いきましょう。

実践編

実践編では、具体的に以下について確認していきます。

  • HTTP 通信では、平文がそのまま見えてしまうこと
  • 理論編のシーケンス図で示した、TLS1.2 ハンドシェイクの各フェーズ ( Client Hello , Server key exchange 等) で送信される情報
  • HTTPS 通信ではデータが暗号化されており、平文の内容を確認できないこと

準備

通信の中身を見るための準備について簡潔に記載します。

不要な方は、通信の中身を見てみる まで飛んでください。

準備1: 証明書の作成

HTTPS 通信では、公開鍵が被証明者のものであることを証明するため、「証明書」を用います。

手元でHTTPS 通信するための準備として、自己証明書を作成しました。

具体的には、以下の手順を実行します。

  • 秘密鍵の生成
  • CSR (Certificate Signing Request, 証明書署名要求) の作成
  • 自己証明書の作成

OpenSSL を使って証明書を作成します。証明書の作成方法については、 食べる!SSL! ―HTTPS環境構築から始めるSSL入門 などを参考にしました。

1-1. 秘密鍵の作成

以下コマンドで生成します。

openssl genrsa 2048 > /path/to/server.key

openssl req -text < server.key コマンドで、確認できます。

RSA Private-Key: (2048 bit)
modulus:
00:b3:a8:59:~
publicExponent: 65537 (0x10001)
privateExponent:
02:2a:4d:ae:~~
prime1:
00:e7:d1:1b:~~
prime2:
00:c6:66:47:~~
exponent1:
73:ad:3d:dc:~~
exponent2:
53:fb:ab:ae:~~
coefficient:
00:dd:02:c8:~~
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
~~
-----END RSA PRIVATE KEY-----

1-2. CSR の作成

以下コマンドで作成します。

openssl req -new -key /etc/~/server.key > ~~/server.csr

以下が確認できます。

Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=JP, ST=Tokyo, L=hoge, O=hogehoge corp., CN=local.hoge/emailAddress=hogehoge@gmail.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:b3:a8:59:~~
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha512WithRSAEncryption
89:84:54:66:~~
-----BEGIN CERTIFICATE REQUEST-----
~~
-----END CERTIFICATE REQUEST-----

1-3. 証明書の作成

以下コマンドで、証明書を作成します。

openssl x509 -req -signkey server.key -in server.csr -out server.crt -extfile SAN.txt

準備2: サーバー立ち上げ

今回は Go を使ってサーバーの立ち上げを行いました。 https フラグを指定することで HTTPS ポートが開かれるようになっています。

package main

import (
"flag"
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}

func main() {
var httpsFlag = flag.Bool("https", false, "bool flag")

flag.Parse()
fmt.Printf("param -https : %t\n", *httpsFlag)

http.HandleFunc("/", handler)

if *httpsFlag {
certFile := "server.crt"
keyFile := "server.key"

fmt.Println("Starting server on port :8443")
if err := http.ListenAndServeTLS(":8443", certFile, keyFile, nil); err != nil {
fmt.Println(err)
}
} else {
fmt.Println("Starting server on port :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println(err)
}
}
}

通信の中身を見てみる

準備が整ったので、通信の中身を見ていきましょう。

通信の中身を見るにあたり「Wireshark」というツールを用いました。ここからダウンロードできます。

HTTP 通信の場合

以下でサーバー立ち上げを行い、

go run main.go

curl で GET リクエストを送ってみます。

curl -i http://localhost:8080

ここで、Wireshark でパケットを確認してみます。
サーバから返される Hello World! というテキストが、平文のまま送信されていることが分かりますね。
http_平文.png

HTTPS 通信の場合

サーバーを立ち上げた後、

go run main.go -https

curl でリクエストを送ります。

curl --tls-max 1.2 --insecure -i https://localhost:8443

※ 自己証明書を使用している場合、 --insecure オプションが必要です。

$ man curl
-k, --insecure
(TLS SFTP SCP) By default, every secure connection curl makes is verified to be secure before the transfer takes place. This option
makes curl skip the verification step and proceed without checking.

When this option is not used for protocols using TLS, curl verifies the server's TLS certificate before it continues: that the
certificate contains the right name which matches the host name used in the URL and that the certificate has been signed by a CA
certificate present in the cert store. See this online resource for further details:
https://curl.se/docs/sslcerts.html

Wireshark でパケットを確認してみます。

通信データが Encrypted Application Data となっています。無事暗号化されていることが分かりますね。

application_data.png

本題: HTTPS 通信の際の TLSハンドシェイクを追ってみる

では、暗号化されるまでに、どんな情報がクライアントとサーバー間でやりとりされているかを、順を追って見ていきます。

必要に応じて、「理論編」の「シーケンス図」も参考にしていただければと思います。

再度 curl でリクエストを送ります。

curl --tls-max 1.2 --insecure -i https://localhost:8443
TLS1.3ハンドシェイクの復号が難しかったため、TLS1.2を指定しています。

(1) Client Hello

クライアント側が、対応している暗号スイートの一覧をサーバーに送っていることが分かります。

また、ランダム文字列 Random も一緒に送信していることが分かります。これはマスタシークレットの生成に使用されます。

client_hello_tls1.2.png

(2) Server Hello

サーバーが、選択した暗号スイート (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) をクライアントに伝えています。

  • 鍵交換アルゴリズム : ECDHE (Elliptic Curve Deffie Helman Ephemeral)
  • 認証: RSA
  • ハッシュアルゴリズム: SHA256
  • 暗号化アルゴリズム: AES128_GCM

また、ランダム文字列 Random も一緒に送信していることが分かります。(1)と同様、マスタシークレットの生成に使用されます。

server_hello_tls1.2.png

(3) Certificate

サーバー側がサーバー証明書をクライアントに送信しています。

  • 証明書作成/検証のためのアルゴリズムとして sha256WithRSAEncryption が採択されていることが分かります
  • クライアントは、以下の手法でサーバ証明書の正当性を検証します。
    • サーバの証明書からハッシュ値を生成する ( hash_a とする)
    • この証明書に付属している電子署名を、公開鍵を用いて復号する ( hash_b とする)
    • hash_ahash_b が一致することを確認する
server_certificate_mask_tls1.2.png

(4) Server Key Exchange

鍵交換アルゴリズムとして ECDHE を使用していることが確認できます。

Server Key Exchangeで、サーバーは named_curve (楕円曲線暗号の事前パラメータ群の名前) として x25519 を選択しています。
Pubkey (公開鍵) も同時に送信しています。(シーケンス図の公開鍵 B_pub に相当)

また、 signature (電子署名) も送られてきていることがわかります。これは、 DH パラメータに対してサーバーの RSA 秘密鍵を適用することで作成された電子署名です。named_curve や pubkey が正当なサーバから送られてきていることを検証するために用いられます。
※ サーバ証明書に付属されている電子署名とは異なるものなので、混乱しないよう気をつけてください。

server_key_exchange.png

(6) Client Key Exchange

クライアント側も、Client Key Exchange で public key を送信しています。 (シーケンス図の秘密鍵 A_pub に相当)

client_key_exchange.png

(7)~(10): Change cipher spec, Finished

Change Cipher Spec で暗号化モードに切り替えます。

また、Finished の Verify Data で、全メッセージのハッシュ値を用いて改ざん検知を行います。

(client 側のものだけ載せます)

client_cipher_spec_finished.png

暗号化後の通信

データが暗号化され、謎の文字列になっていることが分かります。やったね。

application_data.png

おまけ: HTTPS 通信を復号してみる

SSLKEYLOGFILE を指定することで、プリマスタシークレットキーをファイルに書き出すことができます。

SSLKEYLOGFILE=/path/to/sslkeys.log curl  --insecure --tls-max 1.2  -i https://localhost:8443

このプリマスタシークレットキーが書き出されたファイルを、以下のように Wireshark 内の設定で指定してあげることで、データの復号できます。

スクリーンショット_2024-12-29_22.47.07.png

詳しくは Wireshark の Wikiこちらの記事 をご覧ください。

すると、Protocolが TLSv1.2 → HTTP2 に変わっていることが分かります!

Hello World! が get されていることが分かります。無事 (?) 復号されていますね。

decrypted_text.png

まとめ

通信の中身を追ってみたことで、どのように SSL/TLS ハンドシェイクが行われ、セキュアな通信が確立されるかを、実感をともなって理解できました。

また、ハンドシェイク後、実際にデータが暗号化されていることも確認できました。

みなさんも、通信の中身を追ってみると色々と面白いことがわかるかもしれません。

おわり。

参考文献

(※ 理論編に載せてあるものと同じです)

⚪︎全体

SSL/TLS の仕組みを勉強する上で、以下の技術書が非常に参考になりました。

特に、1冊目は非常に体系的かつ網羅的にまとまっているので、SSL/TLS について勉強したい人は持っておいて損はないと思います。2冊目も分かりやすく、SSL/TLS について応用情報程度の知識しかなかった自分でも理解しやすかったです。自己証明書の作成なども、こちらの本を参考にしました。 (ただし、少し古めの本で、TLS1.3 については書かれていません。)

⚪︎ DHの理解

Diffie-Hellman 鍵交換方式については、以下を参考にしました。

⚪︎ Wireshark

wireshake でハンドシェイクを確認する方法については、以下を参考にしました。