
暗号化通信を支える「TLSハンドシェイク」とは?
インターネットで買い物をしたり、SNSを使ったりするとき、誰かに通信内容を盗み見られないように暗号化されていることはご存じでしょうか?でも、この「暗号化」ってどうやって始まるんだろう…と考えたことはありませんか?そのカギとなるのが「TLSハンドシェイク」です。
ちょっと難しそうな名前ですが、実は身近な例にたとえると意外とイメージしやすいんです。
TLSハンドシェイクを「秘密の会話の始まり」にたとえると
TLSハンドシェイクは、ネットワーク上で「暗号化された安全な通信」を始めるための手順です。日常生活で例えるなら、初対面の相手と秘密の話をするために「暗号」を決めるプロセスだと考えてみてください。
たとえば、あなたと友達が遠く離れた場所で秘密の手紙をやりとりしたいとしましょう。でも普通に手紙を書いて郵送したら、途中で誰かに読まれるかもしれませんよね。だから、あらかじめ「手紙は特定のルールで暗号化しておこう」と2人で話し合う必要があります。
TLSハンドシェイクは、この「暗号化のルール」を決める話し合いにあたります。ただし、これをインターネット上で超高速に行っているんです。
TLSハンドシェイクの流れをかんたんに
それでは、実際にどんなやりとりが行われるのか、ざっくり見てみましょう。
1. 「お互いの存在を確認する」
最初に、あなた(ブラウザなど)とサーバーが「私はここにいるよ」と自己紹介をします。これを「クライアント・ハロー」「サーバー・ハロー」と呼びます。
2. 「信頼できる証明書を交換」
次に、サーバーが「私を信じて!これが私の身分証明書だよ」と証明書を送ります。これをもとに、相手が本当に信頼できるかを確認します。
3. 「暗号化のルールを決める」
ここで、双方が暗号化の方式を話し合い、最終的に「これでいこう!」と合意します。この暗号化方式を「暗号スイート」といいます。
4. 「共通のカギを共有」
暗号化通信に必要な「共通鍵」をお互いで共有します。これも、第三者に盗み見られないよう、工夫された方法で行われます。
5. 「通信スタート!」
最後に、やりとりが成功すれば、安全な通信が開始されます。以降の通信内容はすべて暗号化されるので、外部からは読めません。
GolangでTLSハンドシェイクを実装するサーバーとクライアント
以下では、上記の流れを再現するために、サーバーとクライアントを段階的に実装します。それぞれの役割ごとにコードを分割しています。
サーバー側のコード
サーバーは、証明書を用いて安全な接続を提供し、クライアントの通信を受け入れます。
ファイル名:server.go
package main
import (
"crypto/tls"
"fmt"
"log"
)
func main() {
// 1. 「信頼できる証明書を交換」
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatalf("Failed to load server certificate: %v", err)
}
// 2. TLS設定を用意
config := &tls.Config{Certificates: []tls.Certificate{cert}}
// 3. サーバーを起動
listener, err := tls.Listen("tcp", ":8443", config)
if err != nil {
log.Fatalf("Failed to start server: %v", err)
}
defer listener.Close()
fmt.Println("TLS server is running on port 8443")
// 4. 接続を受け付ける
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Connection error:", err)
continue
}
// 5. 「通信スタート!」接続を処理
go handleConnection(conn.(*tls.Conn))
}
}
func handleConnection(conn *tls.Conn) {
defer conn.Close()
fmt.Println("Client connected:", conn.RemoteAddr())
// サーバーからクライアントへのメッセージ送信
conn.Write([]byte("Welcome to the secure server!\n"))
}
クライアント側のコード
クライアントは、サーバーに接続して安全な通信を確立します。
ファイル名:client.go
package main
import (
"crypto/tls"
"fmt"
"log"
)
func main() {
// 1. TLS設定を準備
config := &tls.Config{
InsecureSkipVerify: true, // 証明書の検証をスキップ(本番環境では非推奨)
}
// 2. サーバーに接続
conn, err := tls.Dial("tcp", "localhost:8443", config)
if err != nil {
log.Fatalf("Failed to connect to server: %v", err)
}
defer conn.Close()
// 3. 「お互いの存在を確認する」
fmt.Println("Connected to server:", conn.RemoteAddr())
// 4. 「共通のカギを共有」
// ここでは内部的に行われるため特にコードは必要ありません
// 5. サーバーからメッセージを受信
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Fatalf("Failed to read from server: %v", err)
}
fmt.Printf("Server says: %s\n", string(buf[:n]))
}
実行手順
サーバー証明書 (server.crt) と秘密鍵 (server.key) を用意します。OpenSSLなどで生成可能です。
サーバーコード (server.go) とクライアントコード (client.go) をそれぞれ保存します。
サーバーを起動します。
go run server.go
別のターミナルでクライアントを実行します。
go run client.go
クライアントとサーバー間でTLSハンドシェイクが成功し、サーバーからのメッセージが表示されます。
TLSハンドシェイクがもたらす安心感
このコードを通じて、「お互いの存在確認」から「通信スタート」までの流れが実感できたのではないでしょうか。普段は見えない舞台裏ですが、この仕組みのおかげで私たちは安心してインターネットを利用できるのです。
まとめ:舞台裏を体験してみよう
TLSハンドシェイクは、インターネット通信の安全性を支える重要な仕組みです。Golangで段階的に実装することで、具体的な流れを追体験できます。次回ネットを使うとき、ぜひこのプロセスを思い出してみてください。