ごく普通のWindows PCで、ごく普通に暗号化解除が出来てしまう実例
某市民46万人分の個人情報入りUSBメモリ紛失問題で、パスワード13桁を設定しているから大丈夫発言でTwitter上では大喜利大会になっていましたが、なんとなくノリで乗っかっているものの、実際どのくらいの桁数なら安全なのか、逆に桁数が判明することはどのくらい危険なのか、実はピンと来ていない人も多そうな気がします。
…………ぼくもです。
一応、元IT業界の住人なので知識としては知っています。英小文字+数字の36種を使った6桁のパスワードなら、36の6乗である2,176,782,336(約21億)パターンの組み合わせがあるとか、最近のPCならロープライスモデルでも秒間210万パターンのパスワードを生成できるとか。
つまり、21憶÷210万=1,000秒=約16分で解析しちゃうってことでしょ。
13桁もあれば仮に全部数字しか使ってないとしても10兆通りのパターンがあり、秒間200万パスワードを処理しても1388時間(約58日)かかります。
なーんだ数字だけでもけっこうかかるじゃん、と思うなかれ。これはごく普通のヨワヨワPCの例であって、ガチで解析する人たちがそんなショボいPCを使うわけがありません。今時は一般人でもGPU 6枚挿しのM/Bくらい普通に買います。マイニングPCでせっせと小遣い稼いでる人いるでしょ?
うちの4万円くらいのやっすいグラボを6枚挿すだけでも58日の解析が10日で済む計算になります。30万円以上のハイエンドGPUを複数積んだつよつよPCなら1日以内で解析できちゃうんじゃないかな。まぁあくまで数字しか指定してなかったパターンなので、そんなことはあり得ないと思いますが。
いずれにせよ、ほんの10年程度前までは一般人では試すことすら不可能だったパスワード解析が案外簡単に出来てしまう現実があります。現時点では解析に10年かかるパスワード強度でも、年々その解析速度は上昇し、実際の10年後には数秒で解析できるようになっている可能性もあります。
………で、結局のところ、その一般人でも出来ると言われるパスワード解析ってのはどうやるの? 本当に簡単なの?
と疑問に思ったので、実際に試してみました。
お急ぎの方向けに結論から言うと、クッソ簡単でした。ぼくの暗号化済USBメモリは寝ている間に一晩で解析できましたし、6桁程度のZIPファイルなら10分くらいで解析完了しました。
BitLockerのパスワード解析はZIPに比べると100倍以上時間がかかって実例を出すのが大変なので、今回は手抜きして比較的早いZIPファイルのパスワード解析を例に解説を進めていきます。
かつてのBrute-forceアタック(総当たり攻撃)は泥臭かった
20年くらい前のことですが、某テレビ局の子会社のヘルプデスクで働いていた頃、別部署の方から「ZIPファイルのパスワード忘れちゃったよ~」「なんとかしてよ~」「このくらいできるでしょ~?」「いつも椅子に座ってるだけなんだからたまには役に立てよ~」こんなことを言われたことがあります。(やや誇張)
今では考えられないと思いますが、放送局って報道が一番つよつよで、外回りして足使ってる人間が一番エラく、いっつも椅子に座っている情報システム部門は何やってるのかわからない給料泥棒と思われていたんですね。
そんないつも馬鹿にしているただのヘルプデスクのスタッフが簡単にパスワード解析できるなら、パスワードの意味なんてないでしょ、あなたの頭は飾りですか?梅干しと交換したほうがマシじゃありませんか?…………………と心の中でつぶやきつつも、実際に言えるわけはないので「やるだけやってみます」と解析プログラムをVB(Visual Basic)でちゃちゃっと作りました。開発会社でも何でもないのでプログラミング環境なんてないのだけれど、Officeが入っていればVBAマクロは動きますからね。あとはちょろいもんです。
何のことはない、解凍ソフトのパスワード入力画面にSendKeysという関数でキーボード入力をえんえんと送信するだけ。0~9、a~zの各種組み合わせを試行するわけですが、パスワードを忘れたと言っても、ある程度、桁数や使った文字は覚えていたので、そこに絞り込んで丸1日、そのプログラムを走らせて解析したんですね。
無事、解析完了したので解凍したファイルを渡すと、「やっぱ出来るんじゃねーか。渋々やるんじゃねーよ。」みたいなこと言われてちょっぴりキレそうになりましたけど、まぁどうでもいいです。その会社は後に報道部門が情シスに断りなく自前のノートPCを持ち込んで全社にウィルスをばらまいて社会的に死んだのでもう何も思うところはありません。
ちょっと話が脱線しましたが、むかーしのブルートフォースアタックってこんな感じにアプリ(この例では解凍ソフト)にえんえんとキー入力を送信するようなものでした。ガンダムとかのSFアニメでもよく出てきますよね。テンキーにガバっとかぶせるとピコピコピコピコ!と高速で数字入力してハッキングしちゃう謎装置。あんなイメージ。
だけど、普通そんなのアプリ側が対応するんですよ。
パスワード入力のたびにほんの1~2秒の待ち時間を入れる。これだけでも効果てきめん。本来なら秒間200万回パスワードを入力する能力があろうとも、受け入れ側が1秒待つだけで、200万秒、つまり合計555時間も待たされることになります。
John the Ripperでハッシュ化済パスワードを抽出する
今回パスワードハッキングについて調べていて一番驚いたのがこのハッシュ抽出という方法。アプリにキー入力投げるなんてことやってたら一生終わらないので当たり前っちゃ当たり前なのですが、各種暗号化方法ごとにハッシュ抽出の手段が確立されていることに驚きました。
てっきり、こう、パスワード生成ソフトからzipコマンドやmanage-bdeコマンド(BitLockerで使うコマンドラインアプリ)を呼び出すような感じなのかな~?くらいに考えていましたが、先に暗号化ドライブやファイルからハッシュ値を抽出しちゃうのね…。すごい強引だけど、それが出来るならそのほうが良いに決まっています。
ハッシュとはなんぞや、についてはググってもらいたいですが、簡単に言うとパスワードからハッシュ値は計算できるけど、その逆はできない、という一方通行の関数のこと。一方通行だからハッシュ値が漏れてもパスワードが漏れる心配はない…………………………という建前です。
いえね、例えばパスワード「1234」をMD5という手法でハッシュ化すると「81dc9bdb52d04dc20036dbd8313ed055」なわけです。このハッシュ値から計算式で「1234」という文字列を取得することは不可能なのですが、「81dc9bdb52d04dc20036dbd8313ed055」は「1234」だよ、っていうのを覚えておけば良いですよね。計算はできなくともリストにするの。これが辞書攻撃(Dictionary Attack)というやつなんだけど、パスワードに使われそうな単語のハッシュ値をあらかじめ計算しておいて、突き合わせるってわけです。今回はランダムパスワードの強度が知りたいのでやりませんけど。
というわけで、まずはZIPファイルなりBitLockerなり、暗号化されたデータからハッシュ値を抜き出すために「John the Ripper」を使います。
https://www.openwall.com/john/
うちはWindows上でパスワード解析を行うので、上記ページの 64-bit Windows binaries をダウンロードしました。
John the Ripperでハッシュ抽出
John the Ripperの解凍先は C:\john とします。中にrunディレクトリがあり、このディレクトリの中で実行することが前提となっているみたい。
パスワードを付けた test.zip を用意して、C:\John\run に置きます。
テスト用なのでパスワードは「JavaScriptでパスワードを自動生成するサンプル」のページで6桁のパスワード「9ipr3w」を生成しました。いや、ほら、「123456」とかにしちゃうと何かこう、解析したって感じがしないじゃないですか。
C:\john\run\>zip2john test.zip > test.zip.hash
たぶん一瞬で終わると思いますが、これで test.zip.hash というテキストファイルが出来上がります。
中を見るとこんな感じ。
test.zip/test.txt:$zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*15*9dab9d29c0eb226997aeb5c91ce058467b0134151b*5c031fc18990bf64a4fe*$/zip2$:test.txt:test.zip:test.zip test.zip/test.txt:$pkzip2$1*1*2*0*31*12*0*0*31*63*31*0000*204a*e435286250cb9c9d2c513a4258238a3b888e9dab9d29c0eb226997aeb5c91ce058467b0134151b5c031fc18990bf64a4fe*$/pkzip2$:test.txt:test.zip::test.zip
John the RipperでZIPパスワードを解析
C:\jhon\run\>john.exe test.zip.hash --format=zip
実行結果
Using default input encoding: UTF-8 Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x]) Will run 12 OpenMP threads Proceeding with single, rules:Single Press 'q' or Ctrl-C to abort, almost any other key for status Almost done: Processing the remaining buffered candidate passwords, if any. Warning: Only 78 candidates buffered for the current salt, minimum 96 needed for performance. Proceeding with wordlist:password.lst, rules:Wordlist Proceeding with incremental:ASCII 9ipr3w (test.zip/test.txt) 1g 0:02:47:19 DONE 3/3 (2022-06-25 06:52) 0.000099g/s 168497p/s 168497c/s 168497C/s 9ia6oi..9izk0r Use the "--show" option to display all of the cracked passwords reliably Session completed
寝る前の深夜2:47に実行し、朝6:52に完了していたようです。およそ4時間くらいですね。
6桁のわりにけっこうかかったと思われるかもですが、これを実行したPCは Core i5 10400F + RX6600で、ゲーミングPCとしてはXbox Serires Xに及ばないくらいの低スペックな部類です。
上のログの中に 168497p/s という表示がありますが、これは秒間168,497個のパスワードハッシュを生成可能、という意味だと思われます。
先述のとおり、英小文字+数字の全36種の6桁の組み合わせは全部で2,176,782,336なので、2,176,782,336÷168,497=12,919秒=215分=3.58時間ということで、おおよそ理論値に近いかと。理論値より長くなっているのはパスワードが6桁であるとか英数字しか使っていないなんてヒントは与えていないためですね。
超高速なhashcatでZIPパスワードを解析
超高速なパスワード解析ソフトとしてIT系の記事ではちょくちょく話題になるので、hashcatの名前くらいは聞いたことがあるかもですが、このツールはパスワード解析機能のみで、ハッシュ抽出の手段を持っていないため、どのみちJohn the Ripperによるハッシュ抽出はやっておく必要があります。
hashcatのインストール
インストールというかダウンロードして解凍するだけですが、下記のページから、hashcat binariesをダウンロードして解凍します。
ここでは C:\hashcat に解凍したものとして話を進めます。
hashファイルの書き換え
John the Ripperで抽出した test.zip.hash は下記のとおりですが、前後のコロン部分が残っているとエラーになるため、これを少し書き換える必要があります。
↓これを
test.zip/test.txt:$zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*15*9dab9d29c0eb226997aeb5c91ce058467b0134151b*5c031fc18990bf64a4fe*$/zip2$:test.txt:test.zip:test.zip test.zip/test.txt:$pkzip2$1*1*2*0*31*12*0*0*31*63*31*0000*204a*e435286250cb9c9d2c513a4258238a3b888e9dab9d29c0eb226997aeb5c91ce058467b0134151b5c031fc18990bf64a4fe*$/pkzip2$:test.txt:test.zip::test.zip
↓こう
$zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*15*9dab9d29c0eb226997aeb5c91ce058467b0134151b*5c031fc18990bf64a4fe*$/zip2$
うちはWinZipによる圧縮なので $zip2$~ はじまっていますが、お使いの圧縮ソフトによってここは少し違うはずです。
書き換えたファイル(test.zip.hash)も C:\hashcat へ置いておきます。
hashcatの実行
C:\hashcat>hashcat.exe -m 13600 -a 3 test.zip.hash
John the Ripperと異なり、hashcatはハッシュタイプを自動判別してくれないので、オプションの-mでハッシュタイプを指定する必要があります。
指定できるハッシュタイプの一覧は下記ページを参照。
https://hashcat.net/wiki/doku.php?id=example_hashes
先述のとおり、うちはWinZipを使ったのでハッシュ値が $zip2$~ はじまっており、上の表から13600とわかります。
-a はアタックモードで3はBrute-forceを意味します。
オプションの詳細は下記ページ参照。
https://hashcat.net/wiki/doku.php?id=hashcat
実行結果
C:\hashcat>hashcat.exe -m 13600 -a 3 test.zip.hash hashcat (v6.2.5) starting hipDeviceGetAttribute(): 1 HIP API (HIP 5.0.20451) ======================= * Device #1: AMD Radeon RX 6600, skipped OpenCL API (OpenCL 2.2 AMD-APP (3417.0)) - Platform #1 [Advanced Micro Devices, Inc.] ===================================================================================== * Device #2: AMD Radeon RX 6600, 8064/8176 MB (6732 MB allocatable), 14MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates $zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*15*9dab9d29c0eb226997aeb5c91ce058467b0134151b*5c031fc18990bf64a4fe*$/zip2$:9ipr3w Session..........: hashcat Status...........: Cracked Hash.Mode........: 13600 (WinZip) Hash.Target......: $zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*.../zip2$ Time.Started.....: Sat Jun 25 07:09:22 2022 (18 mins, 51 secs) Time.Estimated...: Sat Jun 25 07:28:13 2022 (0 secs) Kernel.Feature...: Pure Kernel Guess.Mask.......: ?1?2?2?2?2?2 [6] Guess.Charset....: -1 ?l?d?u, -2 ?l?d, -3 ?l?d*!$@_, -4 Undefined Guess.Queue......: 6/15 (40.00%) Speed.#2.........: 2631.3 kH/s (8.55ms) @ Accel:8 Loops:999 Thr:256 Vec:1 Recovered........: 1/1 (100.00%) Digests Progress.........: 2983608320/3748902912 (79.59%) Rejected.........: 0/2983608320 (0.00%) Restore.Point....: 48111616/60466176 (79.57%) Restore.Sub.#2...: Salt:0 Amplifier:23-24 Iteration:0-999 Candidate.Engine.: Device Generator Candidates.#2....: 9kui0e -> 9h1wkf Hardware.Mon.#2..: Temp: 70c Fan: 47% Util: 53% Core:1628MHz Mem:1734MHz Bus:8 Started: Sat Jun 25 07:08:28 2022 Stopped: Sat Jun 25 07:28:15 2022 C:\hashcat>
少々長くてわかりづらいですが、中ほどにある$zip2$~の行の一番右に 9ipr3w と表示され、パスワードが解析されたことがわかります。
そして何より驚きなのが、実行時間「18 mins, 51 secs」の部分。
先ほどのJohn the Ripperでの解析では4時間かかったところ、今回は19分弱ですよ!
解析途中でステータスを見て、2631.3 kH/sとかわけわからんスピード出てるのでもしやとは思いましたが、圧倒的な差でしたね…。
hashcatにヒントを与えると更なる高速化
実はここまできてようやく本題なのですが、パスワードの桁数や、使っている文字種にある程度予測がつく場合、どのくらい高速化するものなのか、というのも知りたかったんです。
C:\hashcat>hashcat.exe -m 13600 -a 3 test.zip.hash -1 ?l?d ?1?1?1?1?1?1
はい、ハテナだらけでわけわからんですよね。
hashcatにはマスク機能があり、例えば ?u は大文字、?l は小文字、?dは数字となっています。このマスクされた値しかパスワードには存在しないよー、と指定してあげるわけですが、これだけだとちょっと足りない。
そこで、カスタムマスクという機能で -1 ?l?d という指定をしています。この-1というのは-1 -2 -3 -4と最大で4つまでのカスタムが設定できるという意味。
?lは英語の小文字、?dは数字を意味しますから、-1は英語の小文字あるいは数字、ということ。それを「?1?1?1?1?1?1」というように6個並べることで、このパスワードは英語の小文字と数字の36種類しか使ってないと思うよー、という指定をしています。
この効果は大きく、
C:\hashcat>hashcat.exe -m 13600 -a 3 test.zip.hash -1 ?l?d ?1?1?1?1?1?1 hashcat (v6.2.5) starting hipDeviceGetAttribute(): 1 HIP API (HIP 5.0.20451) ======================= * Device #1: AMD Radeon RX 6600, skipped OpenCL API (OpenCL 2.2 AMD-APP (3417.0)) - Platform #1 [Advanced Micro Devices, Inc.] ===================================================================================== * Device #2: AMD Radeon RX 6600, 8064/8176 MB (6732 MB allocatable), 14MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates $zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*15*9dab9d29c0eb226997aeb5c91ce058467b0134151b*5c031fc18990bf64a4fe*$/zip2$:9ipr3w Session..........: hashcat Status...........: Cracked Hash.Mode........: 13600 (WinZip) Hash.Target......: $zip2$*0*3*0*e435286250cb9c9d2c513a4258238a3b*888e*.../zip2$ Time.Started.....: Sat Jun 25 08:11:00 2022 (11 mins, 1 sec) Time.Estimated...: Sat Jun 25 08:22:01 2022 (0 secs) Kernel.Feature...: Pure Kernel Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.#2.........: 2569.9 kH/s (8.47ms) @ Accel:8 Loops:999 Thr:256 Vec:1 Recovered........: 1/1 (100.00%) Digests Progress.........: 1732706304/2176782336 (79.60%) Rejected.........: 0/1732706304 (0.00%) Restore.Point....: 48111616/60466176 (79.57%) Restore.Sub.#2...: Salt:0 Amplifier:23-24 Iteration:0-999 Candidate.Engine.: Device Generator Candidates.#2....: 9kui0e -> 9h1wkf Hardware.Mon.#2..: Temp: 70c Fan: 47% Util: 42% Core:1507MHz Mem:1742MHz Bus:8 Started: Sat Jun 25 08:10:56 2022 Stopped: Sat Jun 25 08:22:02 2022
解析時間が11分まで短縮されました。
まとめ
短いパスワードは簡単に解析できるとは聞くものの、本当に誰でも簡単にそんなことが可能なのか知りたかったので試してみました。
ざっくり言うと
- John the Ripperで対象のハッシュを抽出
- ハッシュファイルからコロンの前後を除去
- hashcatにハッシュタイプとハッシュファイルを指定して実行
これだけ。
結果、ゲーミングPCとしては非力な自分のPCでもそれなりの速度で解析できることが判明。
もちろん、パスワードの桁数が増えると10倍どころか100倍1000倍の時間がかかるようになるわけですが、総当たり攻撃のほかに、よく使われる単語に絞って攻撃する辞書攻撃もありますし、なにより本気のハッキングではこんなショボい設備ではなくGPUを何枚も使って実行するだろうし、年々PCのスペックは上がり、パスワード解析速度も更に早くなっていくことでしょう。
過去に「記憶できる程度のパスワードを付けるのはもうやめよう」という記事を投稿しており、パスワードを長くすることの重要性については十分理解していたつもりでしたが、パスワード解析自体がここまでカジュアルに出来るようになっているとは思わず、正直ビビっております。
ネットワーク上のログインなら、サーバー側でいろいろブロックする手段もあるのでまだマシですが、ローカル、特に持ち出し可能なUSBメモリ等のパスワードには本当に気を付けたいですね。