ざっくりSSL/TLS

最近おしごとがインフラ屋さんではなくセキュリティ屋さん的な様相を帯びてきました。その中でも、暗号技術について「これはつかっちゃだめ、これを使いなさい」程度のことは言えるけど、それがなぜなのかはあんまり自信を持って言えなかったのが昨年末くらいの悩みでした。ちょっと勉強したのでまとめます。間違いが多々ありそうなので随時更新予定。

SSL/TLSとは

Secure Sockets LayerないしはTransport Layer Security。トランスポート層とアプリケーション層の狭間のプレゼンテーション層に存在する暗号化プロトコル。狭間なので既存のTCP/IPプロトコルスタックの上下を変更しなくていいのが強み。既存のAPの通信の暗号化も比較的簡単で、FW制御もしやすい。そのかわり、より低階層で暗号化を行うプロトコル、例えばL3のIPsecなんかよりは遅い傾向にある上、コネクションを張らないUDPには対応できない。この辺の利点/欠点はほぼトレードオフです。

SSLとTLSは厳密には別物。SSLは独占技術で、TLSはIETFが管理する世界標準。

  • SSL1:死産
  • SSL2:1994年にNetscape社が開発。発表直後に脆弱だと判明
  • SSL3:1995年にNetscape社が開発
  • TLS1.0:Netscape社の独占技術だったSSLを1999年に世界標準化。SSL3とほぼ同機能だが互換性なし。
  • TLS1.1:2006年リリース
  • TLS1.2:現在の世界標準。2008年リリース
  • TLS1.3:策定中(2018年2月現在はドラフト)

SSL/TLSの要素技術

SSL/TLSは以下の要素技術を組み合わせて通信を暗号化する技術です。SSL/TLSのバージョンの違いとは、使える要素技術が違うことと、その組み合わせ方が違うことです(ここが超ざっくり)。

  1. 認証
  2. 鍵交換
  3. 暗号
  4. MAC(メッセージ認証コード)
  5. PRF(疑似乱数生成器)

SSL/TLSは、まずどの要素技術をどんなパラメータで使うかハンドシェイクし、相手が自分の通信したい相手であることを公開鍵方式で「認証」し、共通鍵を「鍵交換」をします。ハンドシェイクが確立したら、データを「暗号」化しつつ送受信します。暗号化する際には、そのメッセージが改ざんされていないことをハッシュ値(=「メッセージ認証コード」)で確かめながら通信をします。共通鍵の元や共通鍵そのものを生成するには十分にランダムな乱数が必要なので、「PRF」をエッセンスに生成します。

要素技術 1.認証

「認証」は公開鍵暗号化方式でサーバ証明書を検証し、通信相手が間違いなく自分の望む相手であることを確認することです。流れは以下の通り。

  1. サーバはサーバ公開鍵とサーバ秘密鍵のペアを生成します。
  2. サーバは自身のサーバ公開鍵や自身のドメイン名、国名などの情報を合わせてcsr(証明書発行要求)を作成して、権威あるCA(認証局)に送付します。
  3. CAはcsrを受け取ってハッシュ化し、CA自身の秘密鍵でハッシュ化されたcsrに電子署名をします。このcsrと電子署名の二つをサーバ証明書とし、サーバに返します。
  4. サーバはクライアントからの接続要求があった際、サーバ証明書を送付します。
  5. サーバ証明書を受信したクライアントは、そのコンテンツ部分(電子署名以外)をハッシュ化します。
  6. 続いてクライアントはサーバ証明書から電子署名を取り出し、CAの公開鍵でそれを復号します。結果として、サーバ証明書のハッシュ値が得られます。
  7. 5と6のハッシュ値を比較します。CAの公開鍵を信用する限り、この2つが一致すればそのサーバ証明書はCAにより認証されたものだと信用できます。
  8. サーバ証明書のコンテンツ部分からはドメイン名が取り出せます。クライアントは、アクセス先のドメインと、取り出したサーバ証明書に記載のドメインが一致していることを確認します。これにより、そのサーバ証明書は間違いなく自分がアクセスしたいドメイン名のサーバから送付されたことが確認できます。

「認証」の要素技術とは、csrに埋め込む公開鍵をどうやって生成するかということ。DSA、RSA、ECDSAの3つが主です。DSAは1024bitが最大鍵長であり、この長さは安全とは言えないため現在では使いません。現在主流なのは2048bit以上のRSAです。ECDSAはこれから主流になりそうな公開鍵暗号技術です。ECDSAはRSAより短い鍵長で同じ安全性が保たれるため、その分処理が高速になります。

証明書の紛失

サーバ公開鍵とペアになるサーバ秘密鍵が流出した場合、このあとの「鍵交換」の際にクライアントが送ってくる「サーバ公開鍵で暗号化した情報」をキャプチャすることで、復号できてしまうため問題です(キャプチャするためにはクライアントないしはサーバのローカルネットワークに侵入する必要があります)。これは絶対に秘匿にしておかねばなりません。

一方、サーバ証明書の流出については、証明書単体の流出であれば、おちついて証明書の失効手続きを行えば大丈夫です。攻撃者は流出したサーバ証明書を用いてなりすましが可能ですが、サーバ秘密鍵を持っていないため、そのあとにクライアントがサーバ公開鍵で送ってきた情報を復号できないためです(まずい状態なのはかわりありませんが)。証明書と一緒に秘密鍵が流出した場合、一刻も早く失効手続きを行わねばなりません。なりすましが可能かつ、その後にクライアントが送ってくる情報を秘密鍵で復号できるためです。もちろん、そのためにはドメイン名まで詐称する必要があるため、通常クライアントが利用するキャッシュDNSサーバに対してDNSキャッシュポイズニング攻撃を合わせて行う必要があります。

要素技術 2.鍵交換

「認証」が完了すると、クライアントはサーバ証明書内からサーバ公開鍵を取り出すことができます。サーバ公開鍵で暗号された文章はサーバ秘密鍵でしか復号できないので、安全に通信できることはできますが、公開鍵暗号方式はいかんせん遅いため、大きなデータのやりとりは困難です。認証が済み、相手がだれかわかったらより早い共通鍵暗号方式に通信を切り替えたい。そのためには安全に共通鍵をクライアント/サーバ間で共有する、つまり共通鍵を直接やりとりせず、双方で同じ共通鍵を持っている状態にする必要があります。「鍵交換」はそれを実現する技術です。鍵交換アルゴリズムは以下の三つが主。

  • RSA鍵交換
  • DHE(Ephemeral(一時的) Diffie-Hellman)鍵交換
  • ECDHE鍵交換(Ephemeral elliptic curve(一時的楕円曲線) Diffie-Hellman)

RSA鍵交換と、DHE/ECDHE鍵交換はだいぶ趣がことなります。

RSA鍵交換

  1. クライアントが盗聴されても問題ないランダムな値であるプリマスタシークレットを生成します。
  2. クライアントは、サーバ証明書の復号で得たサーバ公開鍵でプリマスタシークレットを暗号化し、サーバへ送付します。
  3. サーバはクライアントから得たプリマスタシークレットをサーバ秘密鍵で復号します(★)。
  4. サーバは復号したプリマスタシークレットから、PRF(後述)を利用しつつ、特定の鍵交換アルゴリズムでマスタシークレット(=共通鍵)を生成します。
  5. クライアントは自分自身でプリマスタシークレットからマスタシークレットを計算します。

RSAはサーバの秘密鍵が流出すると、過去の暗号データすべてが復元可能になる(前方秘匿性がない)という脆弱性を持っているため使いません。第三者は、今は読めない共通鍵暗号文をため込んでおいて、サーバ秘密鍵を奪取した後に、(★)の部分から処理を再開すれば共通鍵を入手でき、貯めこんだ暗号文を復号できます。なおRSAが脆弱なのは鍵交換で利用する場合のみであり、認証のみに使う場合には問題ありません。

DHE/ECDHE鍵交換

RSA鍵交換はだめなので、通常はDHEやECDHEを使います。後者のほうが新しく高速です。なお、DHEは離散対数問題の困難性(ってなんだよ)を利用した暗号アルゴリズムです。クライアント/サーバ双方でプリマスタシークレットを「動的に」生成し、互いに交換してそれをごにゃごにゃして共通鍵を生成します(詳細は説明できないので割愛)。DHEとECDHEはRSAと違い認証の仕組みがないため、必ず認証技術と組み合わせて使います。どこの誰ともわからんやつと共通鍵を共有したら大変です。

Ephemeralな鍵交換

プリマスタシークレットを「動的に」生成することを「一時的(Ephemeral)」といいます。なのでDHEじゃなくて、プリマスタシークレットを「静的に」生成するDHなるものもあります。その場合はSSL証明書に記載のパラメータ(DH係数)の長さの数値の公開値を使用しますが、この長さが短い(1024bit以下)と解読が容易なので脆弱です。その場合はSSL証明書の作り直しか、サーバ側でDHを許可しない設定にして対処します。

以上より、認証&鍵交換は2018年2月現在ECDHE with ECDSAが最も安全で速い。ただしクライアントがこれに対応していないとSSL/TLS通信がまったくできないので、様子を見てDHE with RSA(RSAは認証のみ)なども適宜追加で許可しておく。

要素技術3.暗号

ここまでで認証が済み、暗号化に使う共通鍵を得ました。共通鍵を使って実際にどう暗号化するのか決めるのがここ。一般に以下の三種類があります。

  • ストリーム暗号化方式:ビットまたはバイト単位で順次暗号化していく
  • ブロック暗号化方式:複数のバイトをまとめて暗号化する
  • AEAD:認証機能つきの暗号方式(ここでの「認証」は通信相手ではなく通信内容の完全性検証)

ストリーム暗号

ストリーム暗号化方式で唯一使えるのがRC4。ただしRC4には暗号文の出力に統計的な偏りがあり、解読の可能性がある脆弱性があるため、RFC 7465で使用を禁止されています。

ブロック暗号

ブロック暗号化方式で有名なのはDES、3DES、AESなど。DESは鍵長が短く、総当たりで攻めることができ非推奨です。3DESはDESを3回繰り返す力業です。強度は上がりますが根本的に脆弱なのは同じ。現在は鍵長が長いAESが推奨されています。128bitと256bitが選択可能です。無線LANの暗号化方式でよく見るやつです。

ブロック暗号と暗号利用モード

さて、データをブロック単位で区切って単純に同じ鍵で暗号化すると、同じ平文ブロックからは同じ暗号文が出力されることになります。これを統計的に解析すれば暗号解読の手がかりになるので、ここにランダム性を持たせるために暗号利用モードなるものでなんとかします。ブロック暗号は必ずこの暗号利用モードと組み合わせて使います。有名なのはCBC。CBCはブロックの暗号化を行う前に初期化ベクター(Initialization Vactor, IV)をランダムに生成します。このIVと一番最初の平文ブロックのXORを取り、それから暗号化します。次のブロックは、1つ目の暗号ブロックと今回暗号化する平文ブロックのXORを取り、それから暗号化します。以下繰り返し。ちなみにこのIVの決定方法について、しょっぱなのメッセージ(一連のブロックのまとまり)はいいのですが、それ以降は直前のメッセージの最後のブロックをIVに使っていたため、そのブロックを観察できればつぎのメッセージを復号できてしまうという脆弱性がTLS1.0まで存在しました。TLS1.1ではCBCの利用方法が変わり、IVに関しては脆弱ではなくなりました。

ブロック暗号とパディング

しかし、そもそも一定の長さの平文をブロックに区切ろうと思うと端数がでます。この端数はパディング(詰め物)で埋めるのですが、「ここがパディングだよ」と相手に通知する必要がある以上、パディングは一定の値である必要があります。このため、パディングが含まれるブロックの暗号強度は一気に下がります。これを利用し、ブロックを1ビットずつ解読する攻撃手法がパディングオラクル(詰め物の神託)。パディングオラクルを利用した攻撃手法には様々なものがあります。BEAST、POODLE、Lucky 13など。SSL/TLSレベルでの対策でとられている攻撃もありますが、特にLucky 13攻撃はCBCを使う限り起こりえます。

認証付き暗号

ストリーム暗号は唯一のRC4がだめ、ブロック暗号はパディングが本質的にだめ。じゃあ3つ目はというとAEAD(認証付き暗号)です。そもそもパディングが脆弱なのは、メッセージの完全性を検証するMACを計算したあとに、平文とMACをまるごと暗号化するからです。つまり平文の完全性は検証できても暗号文の完全性は検証できない。これを突くことで1ビットだけ正しい平文、という完全ではない復号を総当たりで試行でする攻撃が可能です。じゃあ暗号化してからMACを計算する一連の流れを整備しようというのがAEAD。AEADにはGCMという暗号利用モードがあり、それをAESと組み合わせることでブロック暗号特有のパディングの脆弱性を排除した認証付き暗号にすることができます。AEADには他にもChaCha20-Poly1305というかわいい名前のものがあります。

結論としては、暗号化方式には認証付きのAES-GCMかChaCha20-Poly1305を使いましょうということ。

要素技術4.MAC

暗号で情報を秘匿するのは大切ですが、それを受け取った相手はその暗号が改ざんされていないことを検知(完全性を検証)しなければなりません。一般的に、元のメッセージと一緒にハッシュ値を送り、受け手はメッセージを同じハッシュ関数でハッシュ化し、送られてきたハッシュ値と比較することで改ざんされていないことを確かめます。このハッシュ関数とその利用方法がMAC。HMAC(Hash-based MAC)という仕組みを使うと、どんなハッシュ関数でもMACに利用できます。ハッシュ関数にはMD5やSHA1、SHA2があります。MD5やSHA1は衝突に成功(あるメッセージのハッシュ値と同じハッシュ値のメッセージを故意に作り出せる)ため非推奨で、TLS1.2から利用不可能になりました。現在ではSHA2を利用し、32bitのメッセージから256bitのハッシュ値を作り出せるSHA-256が主流です。

ちなみにChaCha20-Poly1305でいうとPoly1305がMAC。なお、認証付き暗号利用モードであるGCMは暗号方式自体に完全性検証の仕組みを持つため、MACの実装は必要ありません。

要素技術5.PRF

ところで、共通鍵の元や共通鍵そのものを生成するには十分なランダム性が必要です。このランダム性を持ってくるのがPRF(疑似乱数生成器)。

TLS1.2ではPRFに何を利用するか明示することができますが、デファクトスタンダートとしてHMAC-SHA256が使われています。MACと同じ技術ですが、ハッシュ値を生成する目的が違うことに注意。MACではメッセージの完全性検証のため、PRFでは特定の引数から発散したランダムな値を生成するため。

PRFでは3つの引数(シークレット、シード、一意なラベル)から乱数を生成するためにハッシュ関数を利用します。シークレットは秘密情報、シードはランダムな値、一意なラベルは識別子を意味するそうです。共通鍵そのものの生成の際で言うと、シークレットはプリマスタシークレット、シードはサーバ時刻とサーバが生成するランダムな値、一意なラベルはクライアント時刻とクライアントが生成するランダムな値のことなのかな。確証がないわ。

SSL/TLSによる各要素技術の組み合わせ方

5つの要素技術について、それらの何をどう組み合わせるかがSSL/TLSのバージョン差異だと先述しました。じゃあバージョンごとに何がどう違うのか。SSL2はすぐ脆弱だと判明したため置いておいて、SSL3とそれ以降の違いを見ていきます。

TLS1.0

  • Netscape社1社の実装ではなく、世界標準になりました。
  • PRFが規定されました。TLS1.0ではHMAC-MD5とHMAC-SHA1の排他的論理和としてPRFが実装されました。
  • マスタシークレットの生成が独自手法ではなくPRFを利用するよう変わりました。
  • 完全性の検証に、標準化された新しいHMACを利用するようになりました。それまでは古い脆弱なHMACを利用していました。
  • パディングのフォーマットが厳密に規定されました。SSL3と違いPOODLE攻撃に強くなりました。
  • 暗号スイートからFORTEZZAが外されました。これはアメリカ生まれの暗号装置とそれが利用する暗号スイートです。1企業の独自実装が気に入らなかったのでしょうか。

TLS1.1

  • CBCでIVがメッセージごとに変わるようになりました。2回目以降のメッセージ送付の際に、直前のメッセージの最後のブロックをIVに使っているという脆弱性がなくなりました。
  • TLS拡張が制定されました。従来やりとりできなかった様々な情報をやりとりできるようになりました。1グローバルIPで複数の独自FQDNを持つWebサーバをサポートするSNIや、NWを節約できる「長さが切り詰められたMAC」を利用できるようになりました。

TLS1.2

  • AEADがサポートされました。
  • HMAC-SHA256がサポートされました。
  • DESが削除されました。
  • PRFがHMAC-MD5とHMAC-SHA1の組み合わせでなくHMAC-SHA256単独になりました。
  • PRFを独自に規定できるようになりました。

他にもあると思う。以上、SSL/TLSは5つの要素技術の組み合わせで機密性・完全性を保った通信をできるようにするトランスポート層以上アプリケーション未満のプロトコルだよということでした。

所感

まぁここまでやったらとりあえずSSL/TLSはいいかなと思うけど、不正確な部分は適宜正しい理論を覚えていきたい。ある程度自信をもってSSL/TLSについて話すことができるようになりました。周りは話してもわからない人ばっかだから間違いも指摘されないのが怖いけど。

ちなみに結論として、2018年2月現在おすすめの暗号スイートはTLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA256です。もちろんTLS1.2で。このブログサーバはTLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256だったかな。ECDSAではなくRSAなのは、証明書にLet’s Encryptを利用しており、そこはデフォルトではRSAでサーバ証明書を作るためです。調べたらECDSAでも発行できるそうな。気が向いたらやります。

参考

プロフェッショナルSSL/TLS(紙書籍+電子書籍)

コメントを残す

メールアドレスが公開されることはありません。

*

  • メールアドレス、サイトは必須ではありません。
  • 日本語が含まれない投稿は無視されます。
  • URLはh抜き(ttp://もしくはttps://)で記載してください。