こんにちは。著者のActiveTKです。
今回は、パスワード付きZIPファイルのパスワード解析を実際に実践して、どれくらいの時間がかかるのか予測していきたいと思います。
まさか、ZIPファイルを4桁の数字のみのパスワードで暗号化している方はいませんよね?
一般のPCでも3分あれば解析出来てしまいますよ?
C#で総当たり
まず、C#で4桁の数字のみのパスワードを総当たりして、解析速度(1秒間にテスト出来るパスワードの数)を計測します。
ところが、C#にはパスワード付きZIPを展開する機能がありません。
なので、ZIPの展開はSharpZipLibというライブラリに頼りたいと思います。。
バージョン1
まず最初に考え付くのは、ループ文を利用して、単一スレッドで総当たりをする方法です。
int i = 0; // 現在の数
while (true) // 無限ループ
{
string h = string.Format("{0:0000}", i); // 0埋めをする
bool d = 合ってるか確認する関数(h);
if (d)
{
Console.WriteLine("パスワードは" + h + "です!");
break;
}
i++; // 現在の数を1プラスする
if (i == 最大値) // パスワードが見つからなかった時の処理
break; // ループから抜ける
}
このような、かなり単純なコードです。
このコードでは、5929という数字を解析するのに12分30秒かかり、 解析速度は7.9/s程度です。
つまり、1秒間に約8個のパスワードを試せるという計算です。
数字からすると早そうですが、 果たしてパスワード解析にしては早いのですかね。。?
バージョン2
先ほどのバージョン1を改善したバージョンです。
まず、マルチスレッド化。
先ほどのコードでは、同時に1つしか解析を進行出来ませんでしたが、複数のスレッドを作成して同時に解析する事により、CPUを最大限に活用します。
// スレッドを定義する
var attack = new ThreadStart(() =>
{
while (true)
{
// 先ほどのとほぼ変わらない総当たりコード
}
});
for (int i = 0; i != Environment.ProcessorCount; i++)
// CPUのスレッド数だけ総当たり用のスレッドを作成する
{
new Thread(attack).Start();
}
次に、CPUをフルで活用する為に「プロセスのCPU優先度」を最大に設定します。
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
これでどうでしょうか。
僕が実験してみたPCは4スレッドだったので、C#の総当たりスレッドも4つ生成されました。
「5929」という4桁の数字を解析するのにかかった時間は1分52秒でした。なので、 解析速度は約53.3/sです。
つまり、1秒間に53個のパスワードを試せるという事です。
バージョン1に比べると、ブロッキング時間が軽減出来た事により、速度が数十倍になりました。
バージョン3
無謀なアプリを作っていると、だんだんこんな感情が湧いてきました。
「普通に考えたらスペックが良い方が総当たり速度が速いんじゃね?」と。
なので、バージョン3ではC#を使わずに、PHPでプログラムを書き、超高スペックなサーバー君に処理をお任せしてみます。
もちろん、サーバーに負荷がかかってしまうので所要時間は数分程度が限界ですが。
まずPHPでパスワード付きZIPを展開出来ないんじゃないか?!という疑問がありましたが、ZipArchiveクラスにsetPasswordという関数があり、C#よりも簡単に展開する事が出来ました。
function unzip(string $pw)
{
$zip = new ZipArchive();
$file_mod = 0755;
$unzip_dir = "./Zip-Files/";
if ($zip->open(filename) !== TRUE)
return FALSE;
for ($i = 0; $i < $zip->numFiles; $i++)
if(file_exists($unzip_dir.$zip->getNameIndex($i)))
@unlink($unzip_dir.$zip->getNameIndex($i));
$zip->setPassword($pw);
if ($zip->extractTo($unzip_dir) !== TRUE) {
$zip->close();
return FALSE;
}
$files = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$files[] = $zip->getNameIndex($i);
if(file_exists($unzip_dir.$zip->getNameIndex($i)))
chmod($unzip_dir.$zip->getNameIndex($i), $file_mod);
}
$zip->close();
return $files;
}
上の関数は、ネットで拾ってきたZIPを展開する関数をパスワード指定出来るように編集したものです。
unzip(パスワード);
と実行すれば、パスワードがあっていれば配列、間違っていればfalseが返ってきます。
これをループ文で実行するだけです。
今までと同じく、 解析速度を計測すると、、393.3/sとなりました。
つまり、1秒間に約400個のパスワードを試せるという事です。
まとめ
今までの解析速度から半数解析時間を推測すると、
バージョン1 | バージョン2 | バージョン3 | |
3桁/数字 10^3/2=500 | 63秒 | 9秒 | 1秒 |
4桁/数字 10^4/2=5000 | 10分31秒 | 1分33秒 | 12秒 |
5桁/数字 10^5/2=50000 | 1時間 45分29秒 | 15分38秒 | 2分7秒 |
6桁/数字 10^6/2=500000 | 17時間 34分51秒 | 2時間 36分20秒 | 21分11秒 |
4桁/半角英数字 (10^4+26^4)/2 =233488 | 8時間 12分35秒 | 1時間 13分00秒 | 9分53秒 |
4桁/全半混合英数字 (10^4+26*2^4)/2 =3660808 | 5日と 8時間 43分13秒 | 19時間 4分43秒 | 2時間 35分07秒 |
6桁/全半混合英数字 (10^6+26*2^6)/2 =9885804832 | 39年248日と 10時間 8分20秒 | 5年321日と 16時間 46分02秒 | 290日と 22時間 05分32秒 |
となります。
一週間で半数が解けてしまうかどうかを基準にして、利用可能か判断すると、最低でもパスワードは6桁以上の、半角・全角混合の英数字にするべきだという事です。
なお、そのようなパスワードですらも高性能なサーバーの力を借りれば1年で解析出来てしまうという事です。
なので、パスワードは
・半角・全角の英語と数字を含んだ6桁以上
・1年に1回は変える!
ようにしましょう。