「暗号」で一括りにされがちな Web 技術たち

Web システムでは当たり前に使用されている暗号技術ですが、意外と意識しないとどのような種類の「暗号」をどのような理由で使っているのか分からないものです。

例えばパスワードの検証において、 PHP には [password_hash](https://www.php.net/manual/ja/function.password-hash.php), [password_verify](https://www.php.net/manual/ja/function.password-verify.php) という便利な関数が備わっていますが、システムは申し分ない品質で実装できる開発者であっても、それが厳密には「__暗号__」ではなく、正しくは「__ハッシュ__」であることには気が付いていないかもしれません。

もしくは、DB に平文でパスワードを保存してはならない、という部分だけを理解した開発者は本当の意味での「__暗号__」を用いてパスワードを保存してしまうかもしれません。

そこで簡単にではありますが、Web システムの定番かつ、「__暗号__」で一括りにされがちな技術の特徴、そして選定するにあたってのメリットデメリットを説明したいと思います。

_元々は社内向けとして書いたものですが、Qiita 向けに加筆・修正した文章です。_

# 共通鍵方式と公開鍵式暗号方式
一旦 Web から離れて、なぜ暗号というものが必要なのか考えてみましょう。

そもそも暗号とは、__情報をやり取りする対象者以外から情報を秘匿する__のが目的です。

しかし、お互いが近くにいる場合はヒソヒソと小さな声で話せば良いだけです。声を出すのが難しいなら小さな紙切れに内容を書いて渡すのも良いでしょう。

歴史的に見ても、__暗号が本当に必要になる場面は「物理的に離れた人間が何らかの通信手段を使って秘密の情報を共有する」という状況__です。

手紙だったら郵便配達員、電話だったら回線を提供している業者にでもなりすませば情報は手に入るでしょう。

なので、__「情報そのものが攻撃者の手に渡っても攻撃者が解読できない」のが良い暗号の条件__となります。

 

Web に話を戻しましょう。

Web でも先述した話の流れは変わりません。サービス運営者はログイン済みユーザーから個人情報を受け取り、それをサーバーへ保存します。そして、その情報をハッキングできないかと目論む攻撃者も存在します。

問題は、サービス運営者にとってこのユーザーとは身内でもなければ信頼できるかどうかわからない、しかも膨大な数の不特定多数であるということです。

攻撃者がユーザー登録をしたらどうなるでしょう。

彼は Web サービスから送られてくる暗号を解除する方式（以降「鍵」と表現します）を手に入れたのですから、同じやり方で他ユーザーに向けて送信された暗号を復号できないか試すでしょう。つまり、__暗号を安全に送受信するためには暗号のみならず、その暗号自体を作成する「鍵」も安全に送受信できなくてはならない__のです。

これを解決するために人類は「__公開鍵式暗号__」というものを編み出しました。

 

「暗号」と聞いて大体の人がイメージするものは大抵「__共通鍵式暗号__」というものです。

これは、暗号化と復号に同じ鍵を使用するもので、例えば「アルファベットを一文字後ろにずらす」という手順で作られた暗号は逆の手順である「アルファベットを一文字前にずらす」という手順で復号することができます。これも立派な「共通鍵式暗号」です。

「公開鍵式暗号」は「共通鍵式暗号」と違い、鍵が二種類存在します。誰でも自由に取得できる「__公開鍵__」と、共通鍵と同じく秘匿する必要のある「__秘密鍵__」の二種類です。

 

~~「公開鍵」と「秘密鍵」には誰でも取得できるか否か、という点の他にもう一つ重要な違いがあります。公開鍵で作成された暗号を秘密鍵で復号することはできますが、公開鍵で暗号を復号することはできません。__秘密鍵は復号専門__、__公開鍵は暗号化専門__なのです(実は逆もありうるのですが、これは後述します)。~~

@angel_p_57 さんよりご指摘頂いたので、修正させて頂きます。

> 上述の通り、公開鍵暗号とは次の特徴をもった方式です。
> 逆計算が困難な数学上の問題に基づく暗号方式
> ペアをなす次の2種類の鍵を用いてそれぞれ別々の(非対称な)操作を行う
> 公開鍵 … 広く一般に公開する。
> 秘密鍵 … 個人や特定の機器のみで保持する。

引用元: [2つの公開鍵暗号(公開鍵暗号の基礎知識)#概要](https://qiita.com/angel_p_57/items/897bf94160be8d637585#%E6%A6%82%E8%A6%81)

ここでは公開鍵を暗号化、秘密鍵を復号に用いたケースを考えます。

つまり、ユーザーは暗号を作成して Web サービスに送信することはできますが、暗号を復号することができないということになります。

よってユーザーに成りすました攻撃者は、送信することはできても暗号を解読することはできないのです。
しかも、ユーザーから送信するデータの中に Web サービスにしか予測できないデータを含めれば、攻撃者はデータを送信することすら不可能になります。

このようなメリットから、「ユーザーから重要な個人情報を Web サービスに対して送信する」という用途にはこの「公開鍵式暗号」の暗号技術が広く使われています。

__「公開鍵式暗号」は「共通鍵式暗号」に比べて、必要な計算量が多い__という特徴もメリットでしょう。つまりこれは__総当たり攻撃による解読に時間がかかる__ということを意味しています。

 

逆にデメリットもあります。

「共通鍵式暗号」に比べて計算量が多いということは、それだけサーバーに負荷をかけるということです。

つまり、ユーザーのアクセスごとに暗号化された個人情報を復号するなど、頻度が多い処理には向きません。

そこで__暗号化と復号を行うのが同一人物の場合は依然「共通鍵式暗号」が用いられています__。具体的にはデータベースに個人情報を暗号化して保存するなら、その情報を復号して取り出すのも同じサーバーなので「共通鍵式暗号」が適しています。

 

現代の Web 技術で使用される「共通鍵式暗号」の代表格は「__AES__」と呼ばれる暗号アルゴリズムです。

対して、「公開鍵式暗号」の代表格は「__RSA__」と呼ばれる暗号アルゴリズムでしょう。

これらは一般的に使用されるプログラミング言語や環境で広くサポートされ、かつ使い方を間違えなければ十分堅牢です。

# 暗号と署名
前章で、公開鍵式暗号において公開鍵は暗号化、秘密鍵は復号専用だと説明しましたが、実は逆の用法があります。
すなわち、秘密鍵で暗号化した文章を公開鍵で復号するというケースです。
これも広義での「__暗号__」ですが、厳密にはこの流れを「__署名__」と呼ばれます。

~~※実はこの「秘密鍵で暗号化」という理解は厳密には正しくなく、署名の作成時には「復号」と同等、検証時には「暗号化」と同等の処理を行うとされています。~~
下記記事を書かれた @angel_p_57 に論旨が違うとの指摘を頂きましたので、上記説明は削除させて頂きます。

> 署名一般に関して言えば、「説明案」で「その本質はあくまで(秘密鍵を持つ)本人のみが署名を作れること、(秘密鍵と対になる公開鍵を使って誰でも)署名データの妥当性を検証する手段があること」

引用元: [「電子署名=『秘密鍵で暗号化』」という良くある誤解の話](https://qiita.com/angel_p_57/items/d7ffb9ec13b4dde3357d)

ただし、「秘密鍵の所有者のみが署名を作れる」「公開鍵の所有者が署名の検証を行える」という用法から、この記事ではこのまま説明を続けます。

この部分についてより深く学びたい方は [「電子署名=『秘密鍵で暗号化』」という良くある誤解の話](https://qiita.com/angel_p_57/items/d7ffb9ec13b4dde3357d) の記事が非常に詳しく説明されております。

Web システムにおいて「署名」が必要な代表的具体例は SNS サービスなどが提供する「OAuth」などが代表的です。

これは平たく言うと、個人情報を保有する Twitter などの SNS が外部のサービスに個人情報自体やその操作権を付与する仕組みです。

つまり、先ほどの共通鍵式暗号で触れた「ユーザーから重要な個人情報を Web サービスに対して送信する」という流れとは逆の「Web サービスから重要な個人情報をユーザー・もしくは外部サービスに対して送信する」という流れを安全に実現しています。

__「署名」の原義は本人であることを証明するためのサイン__です。ですから、例えば Twitter 社が「私は Twitter 社である」という署名を作成したら、利用者は公開鍵を使ってそれが Twitter 社による署名だと分かりますが、__同じ署名は秘密鍵を持っていないがために作成することができない__のです。

「私は Twitter 社である」という内容の署名ではなく、もっと具体的な署名でイメージし直してみましょう。発行者は同じく Twitter 社ですが、「ユーザー A に対してツイートを読み取り、またユーザー A としてツイートを行う権利」という内容の署名を発行するケースです。

Twitter を利用したことがある人ならおそらく一度は「以下を〇〇に許可しますか？」という許可を求めるページを見たことがあるでしょう。

よく思い出してください。それは必ず、「__Twitter に登録した個人情報を用いて何かをする Twitter 以外のサービス__と連携する」時に表示されたはずです。

このとき Twitter は外部サービス代わって、ユーザーに特定の操作を許可するかどうかを尋ねてきます。

ここで承認が取れれば、Twitter は外部サービスへ許可証として署名を送信します。外部サービスは以降、この署名を使用してユーザーが「Twitter でログイン」等の機能を実現できるわけです。

 

攻撃者からすれば外部サービスに成りすまそうにも、秘密鍵を持っていないので署名を作成することができません。

署名をやり取りする通信経路を盗聴し、署名そのものを奪おうにも、先述した OAuth においては被害者であるユーザーの PC には署名の送受信がなく、署名データは外部サービスと SNS サービスのサーバー間同士でやり取りされるので盗聴が困難です。

このケースで最も手堅い攻撃方法は真っ当なサービスを作成、SNS にサービスを登録して、被害者ユーザーに連携許可を出させることでしょう。

情報漏洩といえば確かにそうなのですが、この場合 SNS サービスに責はありません。__署名を攻撃者サービスの手に渡す許可をしたのはユーザー本人__だからです。

サービスの開発者は__誰の許可で署名を作成するか__、ユーザーは__誰に情報を操作させるのか__をはっきりさせるようにしましょう。

正しく使えば署名は秘匿された情報を信頼したサービスにのみ共有できる強力な仕組みです。

# 暗号とハッシュ
ここまで説明してきた操作は特定のデータを「鍵」を用いて暗号化し、必要な場合は同じ、もしくは別の「鍵」を用いて復号するというものでした。

 

これから紹介する「__ハッシュ__」もよく似た操作を行います。

しかし、この「ハッシュ」には__「鍵」と「復号」が存在しません__。

あるテキストデータに特定のハッシュアルゴリズムを施すと人間の読めない別のテキストデータに変換されるところまでは同じですが、それを__元のテキストデータに戻すことができない__のです。

 

欠陥品のようにも思えますが、この__「暗号化されたデータから復号が不可能」という特徴こそが「ハッシュ」最大の利点__です。

加えて「ハッシュ」は、ここまで説明してきた暗号では保障されない重要な特徴を持っています。

__あるデータからハッシュを用いて変換したデータは必ず同じデータを生成する__という特徴です。

 

直感的には、元の文章が同じであれば、同じ鍵・暗号アルゴリズムを用いれば同じ暗号文が得られると思いがちです。

しかし、その特徴は攻撃者による解読を容易にさせます。__鍵や暗号アルゴリズムがなくても、複数の暗号化されたデータ間の差からそれらの予想・検証が可能__だからです。

そこで現代的な暗号アルゴリズムは同じ鍵、データを用いても結果となる暗号文がランダムに異なるように設計されたものが多くあります。

先述した共通鍵式暗号である AES も同じ鍵・平文から全く違う暗号文を生成し、元の平文に戻すアルゴリズムです。

 

しかし、ハッシュと呼ばれる処理は同じデータを用いれば同じ変換結果が得られることを保障しています。

これがどう活用されているかというと、代表中の代表がパスワード認証です。

 

Web サービスとして最も流出してはいけない情報とは何でしょう？ ケースによって答えは変わってきますが、大抵の場合はパスワードでしょう。

なぜなら、パスワードが攻撃者の手に渡るということは被害者ユーザーに許されている全ての情報参照・情報操作が可能になるということを意味するからです。

このため、もちろん情報が流出しないようにするのは当然のこととして、もし流出してもパスワードとして使用できないように、ほぼ全ての Web サービスはパスワードをハッシュ化した上で保存しています。

では実際にログインするときは何をしているのかというと、Web サーバーは、__送られてきたパスワードを同じハッシュアルゴリズムでハッシュ化したデータ__と__保存してあるハッシュ化済みデータ__が等しいかどうかで認証を行っているのです。

 

攻撃者は例えデータベースを盗むことに成功し、パスワードとして保存してあるデータを手に入れてもログインを行うことはできません。ハッシュ化したデータを再度ハッシュ化したらまた違うデータになってしまい、元のデータと一致しなくなるからです。

そして__ハッシュには鍵という手がかりもありません。ハッシュデータから元のデータに復号できないという特徴がここで活きてきます__。

 

Web で使用されるハッシュアルゴリズムで代表的なものには MD5 や　SHA256 等があります。

SHA256 やさらに長い計算を必要とする SHA512 は今だに暗号的用途において現役で、現実的な時間で暗号を解くのが不可能と言われていますが、MD5 などのアルゴリズムは現代のコンピュータでは解読が復号が可能なので、安易に暗号的目的で使用すべきではありません。

 

ただし、他と被らないテキストデータの生成、具体的にはユニークなファイル名の作成などには計算量の少ない MD5 などの使用が適しています。

~~厳密にはハッシュは暗号ではなく、単にデータの変換アルゴリズムなので用法によって最適なアルゴリズムを選べるようにしましょう。~~
ハッシュも暗号技術の一部であるという指摘がありましたので修正させて頂きます。
機密性保持に向かないアルゴリズムも存在することに留意頂ければと思います。

# まとめ

僕もこのあたりの勉強や実装を始めたので理解が間違っている部分があるかもしれません。
セキュリティガチ勢の皆様、間違い等ございましたら温かく指摘して頂ければと思います。