インターネットを利用する上で常について回るのがセキュリティの話です。特にパスワードに関するセキュリティの話題は毎日尽きません。私たちは「このパスワードで大丈夫か」「破られはしないか」という不安を抱きながら、眠れない夜を過ごすことになります。

そこでちょっと気分転換をしてみましょう。今日だけはパスワードを破られる側ではなく、破る側に回ってみるのです。いろいろなことが学べますし、きっと楽しいはずです。

パスワードの保存形式

パスワードがどういう形式で保存されているかご存知でしょうか?パスワードは、一般的にはハッシュという形で保存されています。元のパスワードは保存せずに、ハッシュ関数というものを用いて、パスワードからハッシュへと変換してから保存します。

しかし、いったいなぜそんなことをするのでしょう?

実は、パスワードをハッシュに変換してしまうと、ほとんどの場合、元のパスワードには戻せません。つまり、パスワードからハッシュには変換できますが、ハッシュからパスワードには変換できないのです。この一方通行な性質から、ハッシュ関数は一方向性関数と呼ばれます。

こうしてワンクッション置くことで、ハッシュが流出してもパスワードが悪人にバレずに、高いセキュリティを維持することができるのです。

サービス提供側はハッシュを保存しているので、あなたのパスワードのことを全く知りません。あなたがパスワードを忘れた時にもパスワードを教えてくれずに、自分で再設定するハメになるのもこのためです。パスワードがあっているかどうかを見るときには、入力されたパスワードをハッシュに変換して、保存されたハッシュと照合します。

しかしハッシュも万能無敵ではありません。ハッカーたちも、それを破ろうと日夜努力しています。

ハッシュへの攻撃

ハッシュも万能ではない……とは言ってもハッシュからパスワードを復号するのは簡単ではありません。一方向性関数なのですから、簡単に逆変換というわけにはいかないからです。それでも、様々な攻撃方法が考案されています。

最も簡単な攻撃方法は、総当たり(力づく)方式です。最も単純な総当たり方式では、例えば「a」をハッシュ化し、ハッシュとあっているか照合します。次に「b」をハッシュ化して照合します。そして「c」「d」……「z」まで行ったら次は「aa」「ab」と次々に試していきます。「一生やってろ」と言いたくなるようなバカバカしさですが、近年のコンピュータは高性能なので案外バカにはできません。しかし時間がかかりすぎるのも事実なので、実際にはもう少し賢い方法がとられます。

総当たり攻撃をもう少し賢くしたのが辞書攻撃です。ユーザはランダムな文字列ではなく英単語を用いる傾向があるので、辞書の単語を使って解析してみようという試みです。例えば「applebanana」というパスワードは総当たり方式ではとてつもない時間がかかりますが、辞書攻撃ならたった2単語なのであっさり終わってしまいます。

他にもレインボーテーブルという手法もあります。これは攻撃の時に逐一ハッシュ関数で計算するのではなく、あらかじめハッシュ化したリストを持っておき、その中から参照するという手法です。もちろん事前計算に時間はかかりますし、高度に計算されたハッシュには通じにくい手法ですが、単純なハッシュに対しては非常に高速に解析することができます。

JavaScriptでハッシュを解析してみる

ハッシュの仕組みと攻撃の理屈はわかりました。それでは実際にJavaScriptで攻撃を行ってみましょう!今回解析するのは以下の3つのハッシュです:

今回環境として使うのはGoogle Chromeです。できるだけ新しいChromeを用意してください。また、攻撃方法としては最も単純である総当たり方式を採用します。解析時間はかかりますが、実装は単純になります。

HTMLファイルは以下のものを使います:

あとはmain.jsに処理を書いていきましょう。

今回は簡単のためパスワードに使用する文字を数字と小文字のアルファベットのみに絞ります。また、総当たり方式は非常に時間がかかる処理なので、Web Workersを用いて並列処理を実装します。Web Workersについては「Web Workersを用いてJavaScriptをマルチスレッド化する」をご覧ください。

main.jsは以下のようにします:

main.jsの内容はシンプルです。実際に解析処理を行うcracker.jsをWorkerとして起動し、Workerに必要なパラメータを渡します。あとはWorkerから解析結果が届くのを待ち、届いたら表示するだけです。このWorkerをMAX_THREADS個起動します。

各Workerにはできるだけ均等に仕事を割り振りたいので、担当する頭文字を決めます。例えば3スレッドなら各Workerの担当する頭文字は「0-b」「c-n」「o-z」になります。

そして実際に解析を担当するcracker.jsは以下のようにします:

cracker.jsは少し複雑な構造をしています。まずSHA-256という方式でハッシュを作るsha256関数があります。これはWeb Crypto APIを使うだけなので簡単です。

次に指定された長さの英数字のフレーズを順番に生成するgeneratePhrase関数があります。function*という見慣れない関数宣言が見えますが、これはジェネレータを返すジェネレータ関数です。大雑把に説明すると、ジェネレータ関数は一度yieldで値を返した後、またその続きから実行できる関数です。詳しくは「JavaScriptのIteratorとGeneratorを使って反復処理を書く」をご覧ください。

その次にgeneratePassword関数があります。これもジェネレータ関数です。これは1文字〜maxLengthについてgeneratePhraseするだけの関数です。要は「0」「1」「2」…「z」「00」「01」と順番に文字列を返してくれる関数です。

これらを用いてハッシュの解析をします。main.jsからパラメータを受け取ったら処理を開始します。与えられたハッシュ値と合致するフレーズがあれば、そのフレーズをパスワードとしてmain.jsに送信します。

あとはmain.jsが結果を表示してくれます。

さて、これを実際に実行してみましょう!結果が表示されるまでしばらく待ってください。

ハッシュからパスワードを解析することができました!元のパスワードは「abcd」と「1234」と「apple」ですね。「abcd」と「1234」はすぐに出てくると思いますが、「apple」は5桁なので解析に10分ぐらいかかるので、気長に待ってください。

と、こんな感じでパスワードの解析を簡単に体験することができます。こうやってみると、短いパスワードはすぐに解析できて危ないということがわかりますね。また、4文字から5文字になるだけでも解析時間が大幅に増えることから、長いパスワードは非常に有効だということもわかります。

ほんのお遊びですが、パスワードについていろんなことがわかりますね。興味があれば、もっと色々なことを勉強してみましょう。

おまけ:攻撃への対策

こんなに簡単に破られてしまうのならハッシュ化する意味はないのでは?と思うかもしれません。もちろん、実際にはこんな単純なハッシュ関数は使いません。現実には攻撃に対する何かしらの対策が施されています。

攻撃に対する対策の一つは、ソルトと呼ばれる文字列をふりかける手法です。元のパスワードに、ユーザごとに生成したソルト文字列を付与してからハッシュにすることで、元のパスワードよりも強固なものにします。例えばソルトが32文字なら、パスワード「1234」は4文字ではなく、32文字足して36文字のパスワードと同等のハッシュになるのです!

また、ストレッチングと呼ばれる手法もあります。これはパスワードに対してハッシュ関数を何回も、何百回も繰り返しかける手法です。何度も繰り返す分だけ計算時間はかかりますが、単純ながら効果のある方法になります。

セキュリティ専門家たちは、日夜努力して様々な防衛手段を考案しています。こんなお遊び程度のプログラムで破られるほど世の中は単純ではないので、今夜も安心してお眠りください。