背景
最近1ヶ月にメールでzipファイルとパスワードが別々に送信される事が複数回ありました。zipファイルのパスワード暗号化は余り意味が無いと聞いたことがありましたが、具体的にはどのように意味が無いのか知らなかったので、試しに解いてみました。
調べたり試したりしたことを共有します。
使ったもの
- ubuntu20.04をインストールしたPC
zipのパスワードを解読するコマンドをaptコマンドでインストールしやすかったので、ubuntu環境を使いました。
windowsでもstoreからインストールできるubuntu環境で記事の内容を試せると思います。 - ファイルエクスプローラー
右クリックで圧縮メニューを選び、zip圧縮時に詳細オプションでパスワードを入力して暗号化したzipファイルを作りました。 - p7zip
ubuntuに標準で入っている暗号化や複合ができるプログラムです。 - python3
空白文字、シングルクオート、ダブルクオートの扱いがbashだと大変なので、pythonを使いました。
ubuntuなら下記のコマンドでインストールできます。sudo apt install python3
zipの仕様
自分が調べた仕様をまとめます。間違っていればコメントなどで指摘していただけると嬉しいです。
使える文字の種類は表示可能なascii文字95種類
「a-zA-Z0-9」の 26+26+10=62文字「 ~`!@#$%^&*()-_=+[{]}\|;:'"/?.>,<」33文字
合計95文字
つまり、1文字長くなると前の長さの95倍の種類になります。
pkzipの場合パスワードの長さに制限は無いが、既知の平文があると解読される可能性がある
zipのパスワード保護時の暗号化の手法はいくつかあるようですが、ubuntuのzipコマンドの暗号化で使われているpkzip5は100文字でもパスワードが設定でき、2000文字でもできたという書き込みもあるので、長さに制限は無さそうです。下記の記事によると、文字列が分かっているファイルが含まれていた場合、パスワード無しで解読できるようです。
参考: Zip > 既知平文攻撃
AES暗号化の場合は最長64文字
AESの暗号化は文字数制限があるようで、128bit形式は32文字と256bit形式は64文字のようです。参考: Encryption Passwords
AES暗号化はpkzipより頑健らしく、pkzipの既知平文攻撃のような情報は自分が探した範囲では見つけられませんでした。
解読プログラム
既存のパスワード解読プログラムは利用しているライブラリが古くて解読できなかったり、表示が不親切で不安になったりしたので、p7zipを利用してパスワードの探索を行うpythonのスクリプトを作ってみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# inspired by https://github.com/cyberblackhole/7zip-crack | |
import subprocess | |
import sys | |
chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 ~`!@#$%^&*()-_=+[{]}\\|;:'\"/?.>,<" | |
target_zip = sys.argv[1] | |
def popen_check_password(zip_file: str, password: str) -> bool: | |
return subprocess.Popen(["7z", "t", "-p%s" % password, zip_file], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
interval_print_password = 100 | |
count_to_print_password = 0 | |
number_multi_process = 10 | |
def search_brute_force(prefix: str, min_len=1: int, max_len=None: int): | |
len_pass = len(prefix) + 1 | |
index_char = 0 | |
process_and_passwords = [] | |
found_password = None | |
for c in chars: | |
if len(process_and_passwords) >= number_multi_process: | |
process_and_pass = process_and_passwords.pop(0) | |
if process_and_pass["process"].wait() == 0: | |
found_password = process_and_pass["password"] | |
break | |
password = prefix + c | |
global count_to_print_password | |
count_to_print_password = count_to_print_password + 1 | |
if len_pass >= min_len: | |
if count_to_print_password % interval_print_password == 0: | |
print("\r%s" % password, end="") | |
process_and_passwords.append({"process": popen_check_password(target_zip, password), "password": password}) | |
# if check_password(target_zip, password): | |
# return password | |
for process_and_pass in process_and_passwords: | |
if found_password is not None: | |
process_and_pass['process'].wait() | |
elif process_and_pass["process"].wait() == 0: | |
found_password = process_and_pass["password"] | |
if found_password is not None: | |
return found_password | |
if max_len is None or len_pass < max_len: | |
for c in chars: | |
found_password = search_brute_force(prefix + c, min_len, max_len) | |
if found_password is not None: | |
return found_password | |
result_password=search_brute_force("") | |
print("") | |
print("password: %s" % result_password) |
ubuntuなら下記のようなコマンドでダウンロードして実行できます。
wget https://gist.githubusercontent.com/asukiaaa/8a5294f31f5d77217e94e316f091524d/raw/7zipcrack.py
python3 7zipcrack.py 解きたいzip.zip
解読時間: 手持ちのPCだと3文字に最長約30分 -> 計算上6文字だと約50年
手持ちのPCの性能はこちらです。CPU: Intel Core i7-4600 2.1GHz x4
メモリ 7.7GiB
このPCでパスワードが<<<(先ほど紹介したスクリプトで探索する最後の文字3連続)のzipファイルを解読したところ、約30分かかりました。
1文字増えると95倍なので、8文字までだと最大で下記のような時間がかかります。
- 4文字: 2日
- 5文字: 190日
- 6文字: 49年
- 7文字: 4600年
- 8文字: 44万年
試したものの使わなかったもの
john ripper
処理を並列化可能なjohn ripperというプログラム(Ubuntuへのインストール方法はこちら、使い方を解説した記事はこちら)があります。ubuntuならaptでインストールできます。
sudo apt install john下記のようなコマンドで総当りを実行してパスワードを取得できはしました。
zip2john 解きたいzip.zip /tmp/zip.hash
john --incremental /tmp/zip.hash
しかしながら、探索中の文字列が表示されないため情況が分からず不安になるのと、高性能なGPUを使わない手持ちのPCだとpythonでp7zipを並列化したプログラムの方が解読が早かったため、今回の利用は保留しました。
試したものの期待通りに動かない場合があったもの
unzip
ubuntu20.04の場合mファイルエクスプローラーの右クリックでzip圧縮するとpkzip5が適用されるのですが、unzipはpkzip4系のままなので、パスワードが合っていても下記のような出力が出て解凍できませんでした。unzip -P fo poipoi.txt.zip
Archive: poipoi.txt.zip
skipping: poipoi.txt need PK compat. v5.1 (can do v4.6)
fcrackzip
ubuntuならaptコマンドでインストールできるプログラムです。sudo apt install frackzipインストールは手軽で処理は早めで探査中の文字列も表示されて便利なのですが、unzipを利用するため、unzipで解けないzipファイルが扱えませんでした。
解析は下記のようなコマンドで行えます。
fcrackzip -u -b -v -l 1-10 解きたいzip.zip
おわり
p7zipとpythonを組み合わせてzipのパスワードを解読してみました。自分の環境だと4文字までなら最長2日で解析できそうですが、それを超えると厳しいことが分かりました。
パスワードを利用したzip圧縮はpkzipだと既知平文解析で解読されてしまう可能性がありますが、AES暗号方式で10文字以上のパスワードを使っていれば、解読に多大な時間やエネルギーが必要になるため、暗号化の価値がある印象を持ちました。
(とは言え、AES暗号方式に解読方法が見つかって計算量が大幅に減ったり、計算機の能力が引き続き上がっていき既存の圧縮方式が無意味になる未来が来る可能性はあるので、人に見られたく無い情報をzipで暗号化してwebに公開するのはお勧めしません。)
ということで、自分のzip圧縮に対する認識は「パスワードが短かったら意味が無いものの、10桁以上あればそれなりに保護されている」となりました。
参考
Multiple Ways to Crack ZIP File PasswordsCTFのWebセキュリティにおけるPassword Cracking, ハッシュ, 暗号化
unzip で "need PK compat. v5.1 (can do v4.5)" と言われて解凍できない件
How long should zip encryption password be for it take 10 years to crack?
Encryption tab (WinZip settings)
2 件のコメント :
なるほど、参考になります。今のPCの延長だと現実的に総当たりで解読するのは難しそうですが、もしかしたら10年ほどしたら量子コンピュータでの暗号解析が現実的になるかもしれない…と考えると、時間が経っても読まれたら困るファイルのPPAPは避けた方が良さそうですね。
コメントありがとうございます。
PPAPは「パスワード暗号化したzipとパスワードを別で送る手法」のことを意味するのですね。
Pen pineapple apple pen以外の意味があると認識しました。
コメントを投稿