はじめに
今回は、HTTPSによる暗号通信を可能にするTLSプロトコルと、TLSで使用される暗号技術について説明してきます。本記事では、以下のキーワードをカバーしていきます。
【本記事のキーワード】
HTTP、HTTPS、SSL/TLS、TLS1.2、TLS1.3、TLSハンドシェイク、0-RTT
対称暗号(共通鍵暗号)、公開鍵暗号、Diffie-Hellman鍵交換、メッセージ認証コード、デジタル署名、公開鍵証明書
HTTPとHTTPS
HTTPは、WebブラウザとWebサーバの通信に使用されるプロトコルです。しかし、HTTPでは通信が暗号化されないため、第三者によって通信データが盗聴・改ざんされる可能性があり、セキュリティ上の問題があります。そこで、通信内容を暗号化することで、より安全な通信を実現するHTTPSが現在広く使用されています。
HTTPSは、SSL/TLSを使ったHTTP通信で、HTTPをSSL/TLSの上に乗せて通信を行うことで、安全な通信を実現します。SSLは脆弱性が発見されたため、現在はSSLの新しいバージョンに相当するTLSが使用されています。本記事の内容は、主にTLS1.2に基づいています。
TLSの概要
TLS(Transport Layer Security)は、OSI参照モデルの第6層であるプレゼンテーション層に位置する暗号化プロトコルです。TLSはデータの機密性、完全性、認証を確保し、インターネット上での安全な通信を可能にします。SSL/TLSを用いたHTTP通信では、URLがhttp
ではなくhttps
から始まります。
TLSで使用される暗号技術
通信の機密性を保証するために対称暗号、対称暗号で使用する秘密鍵を相手と安全に共有するために、公開鍵暗号・Diffie-Hellman鍵交換、改ざんを検出するためにメーセージ認証コード、相手を認証するためにデジタル署名が使用されます。
TLSは、上記のような暗号技術を組み合わせることで、以下を実現します。
- メッセージの暗号化(機密性)
- 第三者によるメッセージの改ざんの検出、メッセージの認証(完全性)
- 通信相手の認証(認証)
以下では、それぞれについて詳細に見ていきます。
メッセージの暗号化
メッセージを暗号化することで、通信データが第三者に盗聴されても解読されず、メッセージの機密性を守ることができます。この機密性を実現するために、対称暗号と公開鍵暗号が使用されます。
対称暗号(共通鍵暗号)
対称暗号(共通鍵暗号)は、暗号化と複合化で同じ鍵(秘密鍵)を使用する暗号方式です。メッセージの送信者と受信者が事前に鍵を共有することで、暗号通信が可能になります。TLSのメッセージの暗号化にも対称暗号が使用されます。
しかし、対称暗号では送信者・受信者間でどのように鍵を安全に共有するかという鍵配送問題が発生します。この問題を解決するために、公開鍵暗号やDiffie-Hellman鍵交換と呼ばれる方法で鍵を安全に交換します。
主要な対称暗号アルゴリズム:AES
公開鍵暗号
公開鍵暗号は、暗号化と復号化で別の鍵を使用します。暗号化の際には一般に公開されている受信者の公開鍵を用い、復号化の際には受信者だけが持つプライベート鍵を使用します。
公開鍵暗号を使用することで、以下のように対称暗号の秘密鍵を安全に共有することができ、鍵配送問題を解決することができます。
主要な公開鍵暗号アルゴリズム:RSA
Diffie-Hellman鍵交換(ディフィー・ヘルマン鍵交換)
Diffie-Hellman鍵交換は、送信者・受信者間で2つの素数を交換・共有し、その数を元に対称暗号で使用する秘密鍵を計算によって作り出します。これにより送受信者間で秘密鍵の共有を安全に行うことができます。
この2つの素数は盗聴者に知られても問題はありません。それは、盗聴者が2つの素数から秘密鍵を計算するには、離散対数問題という非常に難しい数学の問題を解く必要があるからです。詳細は、以下をご参照ください。
メッセージの改ざん検出・メッセージの認証
通信中にメッセージが第三者によって改ざんされたり、第三者が送信者になりすます可能性があります。改ざんを検出し、メッセージが正しい送信者からのものであることを確認するために、メッセージ認証コードという技術が使用されます。
メッセージ認証コード
メッセージ認証コード(MAC) は、送信者・受信者間で共有した秘密鍵とメッセージの2つを入力として、MAC値を出力する関数です。送信側で計算したMAC値と受信側で計算したMAC値が一致すれば、メッセージが同じ秘密鍵を持つ正しい送信者からのものであり、通信途中で第三者によって改ざんされていないことが確認できます。
メッセージ認証コードでは、秘密鍵を持つすべての人が同じMAC値を計算できるため、送信者を特定することができません。例えば、自分以外の2人が同じ秘密鍵を持っている時、自分が受信したメッセージを送信した人を特定することができません。そのため、送信者を特定するためにはデジタル署名が必要になります。
また、対称暗号の時と同じように鍵配送問題が発生するため、公開鍵暗号やDiffie-Hellman鍵交換を使用して安全に秘密鍵の共有を行う必要があります。
メッセージ認証コードは、メッセージの正真性(完全性) と認証を提供します。
通信相手の認証
通信相手が本物であるかを確認し、通信相手を特定するために、デジタル署名という技術が使用されます。TLSでは、通信相手のサーバが本物であることを確認するために、サーバの公開鍵にデジタル署名が施されたサーバ証明書を使用します。逆に、サーバ側がクライアントを認証するためにクライアント証明書を使用します。
デジタル署名
デジタル署名は、公開鍵暗号とは逆の手順で行われます。
送信者は自分のプライベート鍵でメッセージを暗号化(署名)し、署名を作成します。受信者は受信した暗号文(署名)を送信者の公開鍵で復号化することで、署名の検証を行います。
デジタル署名を作成するには送信者のプライベート鍵が必要になり、プライベート鍵は送信者だけが持っているため、送信者の特定が可能となります。
公開鍵証明書
公開鍵暗号やデジタル署名などで公開鍵を使用してきましたが、その公開鍵は本当に通信相手のものなのでしょうか。公開鍵の正当性を保証するために、公開鍵証明書が使用されます。
公開鍵証明書は、特定の個人、組織、サーバなどの公開鍵が本物であることを保証するためのもので、その人の個人情報や公開鍵が記載されています。そしてこの証明書には、認証局によるデジタル署名が施されます。認証局は、「公開鍵が確かにその人のものである」ということを保証するためにデジタル署名を作成します。
認証局(CA)
デジタル証明書を発行し、公開鍵が本物であることを保証する信頼できる第三者機関のことです。
TLSの詳細
TLSプロトコルは、主に「TLSレコードプロトコル」と「TLSハンドシェイクプロトコル」からなり、二段構えの構成になります。
TLSレコードプロトコル
TLSレコードプロトコルは、メッセージの圧縮、認証、そして対称暗号を使用したメッセージの暗号化を行います。
以下の5つのステップを通して、送信データを作成します。
- メッセージを複数の短い部分(フラグメント)に分割する
- フラグメントごとに圧縮を行う
- 圧縮したフラグメントにメッセージ認証コード(MAC値)を付加する
- 圧縮したフラグメントとMAC値を合わせて対称暗号で暗号化する
- 暗号文にヘッダを追加する
対称暗号とメッセージ認証コードで使用する共有鍵は、下記のハンドシェイクプロトコルを使用して交換します。
TLSハンドシェイクプロトコル
TLSハンドシェイクプロトコルは、ハンドシェイクプロトコル、暗号仕様変更プロトコル、警告プロトコル、アプリケーションデータプロトコルという4つのサブプロトコルに分かれます。
ハンドシェイクプロトコル
ハンドシェイクプロトコルは、今後の暗号通信に必要な情報をクライアント・サーバ間で交換するためのプロトコルで、暗号方式の取り決めを行います。具体的には、暗号通信で使用する共有鍵を生成したり、通信相手を認証するために証明書を交換したりします。
詳細は、後述の「TLSハンドシェイクの概要」以降をご参照ください。
暗号仕様変更プロトコル
ハンドシェイクプロトコルによって使用する暗号方式が決まると、暗号仕様変更プロトコルが暗号方式の変更を通信相手に伝えます。「今後の通信では、合意した暗号方式を使用します」という合図・宣言のようなものです。
警告プロトコル
警告プロトコルは、エラーが発生した際に、そのことを通信相手に伝えます。具体的には、復号化の失敗やMAC値の不一致、そして証明書の認証に失敗した場合などに、エラーを通信相手に通知します。
アプリケーションデータプロトコル
アプリケーションデータプロトコルは、TLSの上に乗っているアプリケーションのデータ(HTTPリクエスト・レスポンスなど)を、通信相手に安全に転送します。具体的には、アプリケーションから送信依頼されたデータを、アプリケーションデータプロトコルが受け取り、下位層のTLSレコードプロトコルに渡すことで暗号化を依頼します。
TLSハンドシェイクの概要
TLSハンドシェイクでは、クライアント・サーバ間でメッセージをやり取りすることで、以下を行います。
1. 使用するバージョンや暗号の決定
使用するTLSのバージョン(TLS1.2、1.3など)を決定する
使用する暗号スイート(暗号アルゴリズムのセット)を決定する
など。
2. 鍵交換
対称暗号で使用するセッション鍵(秘密鍵)を生成する
3. 認証
サーバ証明書と認証局のデジタル署名を使用して、サーバの公開鍵が正当であること、サーバが本物であることを確認する
TLSハンドシェイクの流れ(TLS1.2)
ここでは、TLSハンドシェイク(TLS1.2)の流れを詳細に見ていきます。
TLSハンドシェイク(TLS1.2)の全体像
1. ClientHello(クライアント→サーバ)
クライアントからサーバにClientHelloメッセージを送信することで、ハンドシェイクを開始します。この際、クライアントからサーバに以下の情報が送信されます。
- 使用できるTLSのバージョン
- 使用できる暗号スイート
- 使用できる圧縮方法
- クライアントランダム(クライアント側で生成した予測不可能な乱数)
など。
クライアントはClientHelloを使用して、「これが私のサポートしているTLSのバージョン、暗号スイートです。どれを使いますか?」とサーバに提案します。
2. ServerHello(サーバ→クライアント)
ClientHelloへの返答として、サーバがServerHelloメッセージを送り返します。以下の情報をクライアントに送信します。
- 使用するTLSのバージョン
- 使用する暗号スイート
- 使用する圧縮方法
- サーバランダム(サーバ側で生成した予測不可能な乱数)
など。
サーバはClientHello内で提案されたオプションと自分が使用できるTLSのバージョンや暗号スイートを照らし合わせ、「それでは、〜を使用して暗号通信を行いましょう」といった内容のServerHelloを返送します。
3. Certificate(サーバ→クライアント)
サーバは、サーバ証明書をクライアントに送信します。この際、サーバ証明書にデジタル署名を行った認証局(中間CAやルートCA)の証明書も一緒に送信します。クライアントは、自身が保有する認証局の公開鍵を使用して、サーバ証明書の検証を行います。検証が成功すると、サーバ証明書とサーバの公開鍵が本物であることが確認できます。
4. SeverKeyExchange(サーバ→クライアント)
鍵交換を行うためのメッセージで、追加の情報が必要な場合のみ送信されます。
例えば、暗号スイートが鍵交換方式としてDiffie-Hellman鍵交換を用いる場合、公開値(GとP)とサーバの公開鍵がSeverKeyExchangeで送信されます。この時、サーバは事前に公開値(GとP)を生成し、サーバの公開鍵とプライベート鍵の鍵ペアを作成しておく必要があります。
鍵交換でRSAを使用する場合は、ServerKeyExchangeは送信されません。
5. CertificateRequest(サーバ→クライアント)
サーバがクライアント証明書を要求するためのメッセージです。クライアント認証が必要ない場合は、このメッセージは送信されません。
6. ServerHelloDone(サーバ→クライアント)
ServerHelloからの一連のメッセージの終わりを伝えます。
7. Certificate(クライアント→サーバ)
5.CertificateRequestでクライアント証明書を要求された場合のみ、クライアントはCertificateメッセージでクライアント証明書を送ります。
8. ClientKeyExchange(クライアント→サーバ)
暗号スイートが鍵交換方式としてRSAを用いる場合は、クライアント側で生成したプレマスターシークレットという46バイトの乱数を暗号化して送信します。暗号化には、3. Certificateで取得したサーバの公開鍵が使用されます。
プレマスターシークレットをクライアント・サーバ間で共有し、この値から共通のマスターシークレットという値を計算します。マスターシクレットをもとにして、対称暗号の鍵やメッセージ認証コードの鍵を作成します。これによりクライアント・サーバ間で秘密鍵の共有が完了します。
Diffie-Hellman鍵交換を用いる場合は、クライアントはClientKeyExchangeメッセージで自身の公開鍵を送信します。この際、クライアント側は事前に、4. SeverKeyExchangeで受け取った公開値(GとP)と、自身のプライベート鍵から公開鍵を作成しておきます。
サーバとクライアントはそれぞれのプライベート鍵と相手の公開鍵を用いて、同一のセッション鍵を計算します。これにより暗号通信で使用する秘密鍵の共有が完了します。
9. CertificateVerify(クライアント→サーバ)
7.Certificateでクライアント証明書を送信した場合のみ、CertificateVerifyメッセージを送信します。このメッセージは、クライアントが確かにクライアント証明書のプライベート鍵を持っていることをサーバに通知する役割があります。
具体的には、ハンドシェイクプロトコルでやり取りしたメッセージのハッシュ値をとり、それにクライアントのプライベート鍵でデジタル署名を行います。そして、そのデジタル署名をCertificateVerifyメッセージでサーバに送信します。サーバはクライアントの公開鍵を使用してデジタル署名を検証することで、クライアントが確かに正しいプライベート鍵を持っていることを確認することができます。
10. ChangeCipherSpec(クライアント→サーバ)
これまでハンドシェイクプロトコルを使用して、クライアント・サーバ間で暗号通信に必要な情報の交換を行ってきました。ここで、暗号仕様変更プロトコルのChangeCipherSpecメッセージを送信することで、「これから暗号通信を始めます!」という合図を送ります。
11. Finished(クライアント→サーバ)
Finishメッセージで、ハンドシェイクプロトコルの終了を伝えます。このメッセージは、TLSレコードプロトコルによって暗号化されて送信されます。
この時、ハンドシェイク中にやり取りされたメッセージからハッシュ値を計算し、サーバに送信します。サーバ側でこの値を検証することで、ハンドシェイク中に中間者攻撃や改ざんが行われていないかを確認します。
12. ChangeCipherSpec(サーバ→クライアント)
今度は、サーバがChangeCipherSpecを送信し、暗号通信開始の合図を出します。
13. Finished(サーバ→クライアント)
サーバからもFinishメッセージで、ハンドシェイクプロトコルの終了を伝えます。このメッセージは、TLSレコードプロトコルによって暗号化されて送信されます。
クライアント側でも同様に、Finishedメッセージで送られてきたハッシュ値の検証を行うことで、ハンドシェイクが適切に行われたかを確認します。
14. アプリケーションデータの送信
以降のアプリケーションデータの送受信では、アプリケーションデータプロトコルとTLSレコードプロトコルを用いて、暗号通信が行われます。
TLSハンドシェイクの流れ(TLS1.3)
TLS1.3は、TLSプロトコルの最新バージョンです。TLSハンドシェイク(TLS1.3)の流れも見ていきたいと思います。
TLSハンドシェイク(TLS1.3)の全体像
1. ClientHello(クライアント→サーバ)
TLS1.2と同様に、クライアントからサーバにClientHelloを送信することで、ハンドシェイクを開始します。TLS1.3では、ClientHelloに含まれる情報量が増加し、以下の情報が送信されます。
- 使用できる暗号スイート
- クライアントランダム(クライアント側で生成した予測不可能な乱数)
- 拡張フィールド(
supported_groups, key_share, supported_versions
などのパラメータを持つ)
など。
ここで注目すべきは、拡張フィールドです。supported_versions
でクライアントが対応するTLSのバージョンのリストを送信します。また、supported_groups
で鍵交換方式の候補をいくつか提示し、key_share
パラメータで鍵交換用のパラメータを送信します。
TLS1.3では、TLS1.2とは異なり、ClientHelloで鍵交換に必要な情報をサーバ側に送ります。
2. ServerHello(サーバ→クライアント)
ClientHelloへの返答として、サーバが以下の情報を含んだServerHelloを送り返します。
- 使用する暗号スイート
- サーバランダム(サーバ側で生成した予測不可能な乱数)
- 拡張フィールド(
key_share, supported_versions
などのパラメータを持つ)
など。
サーバ側は、拡張フィールドのsupported_versions
で通信に使用するTLSのバージョンを指定します。そして、key_share
を使用して鍵交換に必要な情報をクライアントに送信します。
ClientHello/ServerHelloによって、クライアント・サーバ間で鍵交換に必要な情報の共有が完了しました。この情報をもとに、双方は対称暗号で使用する共通のセッション鍵(秘密鍵)を作成することができます。これにより、以降の通信は全て暗号化されます。
3. EncryptedExtensions(サーバ→クライアント)
EncryptedExtensionsメッセージは、ServerHelloの送信直後に暗号化されて送信されます。EncryptedExtensionsは、ClientHello内の拡張フィールドへの返答で、サーバが使用に同意した拡張機能の情報が含まれます。
4. CertificateRequest(サーバ→クライアント)
クライアント認証が必要な場合に、サーバはCertificateRequestメッセージを送信します。必要ない場合は、省略されます。
5. Certificate(サーバ→クライアント)
TLS1.2の時と同じように、サーバ証明書と、サーバ証明書にデジタル署名を行った認証局(中間CAやルートCA)の証明書をクライアントに送信します。
クライアントは、自身が保有する認証局の公開鍵を使用して、サーバ証明書の検証を行います。検証が成功すると、サーバ証明書とサーバの公開鍵が本物であることを確認できます。
6. CertificateVerify(サーバ→クライアント)
サーバ証明書の公開鍵とペアになるプライベート鍵を持っていることをクライアント側に通知する役割があります。ハンドシェイク中のメッセージのハッシュ値をとり、その値にサーバのプライベート鍵でデジタル署名を行います。そして、そのデジタル署名をCertificateVerifyメッセージでクライアントに送信します。
7. Finished(サーバ→クライアント)
サーバはCertificateVerifyの送信後に、Finishedメッセージをクライアントに送信してハンドシェイクの終了を伝えます。この時、ハンドシェイク中に交換したメッセージからMAC値を計算し、クライアントに送信します。クライアントはこの値を検証することで、ハンドシェイク中に中間者攻撃や改ざんが行われていないかを確認します。
8. アプリケーションデータの送信
この段階でサーバは、クライアントからのFinishedメッセージの受信を待たずに、クライアントに対してアプリケーションデータを送信することができます。ただ、認証されていないクライアントにデータを送信している点には注意が必要です。
9. Certificate(クライアント→サーバ)
サーバからのCertificateRequestメッセージによってクライアント認証を要求された場合は、Certificateメッセージで、クライアント証明書とそれにデジタル署名を行なっている認証局の証明書を送信します。
10. CertificateVerify(クライアント→サーバ)
クライアント証明書の公開鍵とペアになるプライベート鍵を持っていることを、サーバ側に伝える役割があります。ハンドシェイク中のメッセージのハッシュ値をとり、それにクライアントのプライベート鍵でデジタル署名を行い、サーバに送信します。
11. Finished(クライアント→サーバ)
Finishedメッセージを送信してハンドシェイクを終了します。サーバ側でもFinishedメッセージで受信したMAC値を検証することで、ハンドシェイクが適切に行われたことを確認します。
12. アプリケーションデータの送信
以降のアプリケーションデータの送受信では、アプリケーションデータプロトコルとTLSレコードプロトコルを用いて、暗号通信が行われます。
TLSハンドシェイクはいつ発生するのか?
TLSハンドシェイクは、TCPハンドシェイクにより通信相手との間にコネクションが確立された後に行われます。
TLS1.2 vs TLS1.3
TLS1.2からTLS1.3になることで、より高速で安全な暗号通信が実現します。以下が主要な変更点になります。
ハンドシェイクの高速化
TLS1.3のハンドシェイクでは、ClientHello/ServerHelloで送信できる情報量が増加したため、TLS1.2のハンドシェイクと比べて、クライアント・サーバ間での通信回数を減らすことができます。
具体的には、TLS1.2ではハンドシェイク中に2往復のやり取りを行うのに対し、TLS1.3では1.5往復のやり取りを行います。これにより、ハンドシェイクにかかる時間を短縮し、高速化を実現しています。
より安全な暗号スイート
TLS1.2では脆弱性のある古い暗号アルゴリズム(鍵交換でのRSAなど)をサポートしていたため、セキュリティ上の脆弱性が指摘されていました。TLS1.3では、そういった古い暗号アルゴリズムのサポートを廃止し、脆弱性が発見されていない暗号アルゴリズムのみをサポートすることで、より安全な暗号通信が可能になります。
0-RTT(ゼロラウンドトリップタイム)
以前にクライアントがサーバに接続したことがある場合(サイトを訪れたことがある場合)、クライアントとサーバは、ClientHello/ServerHelloでいきなり暗号化されたアプリケーションデータを送信することができます。
以前のTLSハンドシェイク中にサーバからクライアントにNewSessionTicketメッセージでPSK(事前共有鍵・Pre-shared key)を送信しておきます。そうすることで、次のTLSハンドシェイク時に、クライアントはそのPSKでアプリケーションデータを暗号化することで、ClientHelloメッセージと共に暗号化されたデータを送信することが可能になり、サーバと即座に暗号通信を開始できるようになります。
参考
暗号技術入門 第3版 (結城浩)
TLS | SSLハンドシェイクのプロセスは? (CloudFlare)
TLS 1.3を使用する理由とは? (CloudFlare)
パケットキャプチャで理解する TLS1.3 (Zenn)
The Transport Layer Security (TLS) Protocol Version 1.2 (RFC 5246)
The Transport Layer Security (TLS) Protocol Version 1.3 (RFC 8446)
The TLS 1.2 Protocol (IBM)
The TLS 1.3 Protocol (IBM)
Session resumption with a pre-shared key (IBM)
TLS 1.3 Handshake: Taking a Closer Look (hashedout)