この記事はBrainPad Advent Calendar 6日目の記事になります
こんにちは@nissy0409240です
BrainPadでエンジニアをしています
2021年もあと一ヶ月を切りましたが
皆様いかがお過ごしでしょうか
道中のコンビニで買った3色パンとルイボスティを飲みながらせっかちなペースで歩みを進め
ひと夏の長さに思いを馳せながら、ありがちな日常を取り戻すきっかけは何なのか
考えていた時見つけた友人に話しかけたつもりが他人のそら似でした
偶然を言い訳にして博物館に到着した時は駅前より人も少なく
ゆっくりと咲く花のような時間が流れていました
混み込みしていない圧縮されることのない空間でマスク越しに深呼吸をし
私の色を取り戻す感覚がした休日でした
そんな晴れやかな休日を過ごした後に書くエントリーでは
zip圧縮されたファイルについて書こうと思います
圧縮形式
ビッグデータという言葉のもの珍しさも薄れつつある今日この頃ではありますが
大量のデータのやり取りにおいて現在もファイル圧縮は一般的な手法であることに変わりはありません
よく見る圧縮形式は以下のようなものが存在します
圧縮形式 | 拡張子 | 説明 |
---|---|---|
ZIP | .zip | 一般的に使われている圧縮形式。そこまで圧縮率は高くない |
GZIP | .gzip .gz .tar.gz .tgz | ZIPより圧縮率は高い。テキストファイルなら9割近く圧縮可能 |
BZIP2 | .bzip2 .bz2 .tar.bz2 | GZIPより圧縮率は高い。圧縮・展開ともに時間がかかる |
主要なプログラミング言語ではこれらの形式に対応しており
解凍するためのモジュールも実装されています
例えば.zipファイルは以下のように簡単に解凍出来ます
import zipfile
with zipfile.ZipFile('/temp/test.zip') as f:
f.extractall('/temp/out/')
パスワード付きだと以下の通りです
import zipfile
with zipfile.ZipFile('/temp/test.zip') as f:
f.extractall('/temp/out/', pwd=b'testpass')
上記のコードはリファレンスですぐに見つかるレベルのコードです
しかし、実行した時に以下のような挙動をするケースに出会いました
>>> import zipfile
>>> with zipfile.ZipFile('/temp/test_aes.zip') as f:
... f.extractall('/temp/out', pwd=b'testpass')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 1633, in extractall
self._extract_member(zipinfo, path, pwd)
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 1686, in _extract_member
with self.open(member, pwd=pwd) as source, \
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 1559, in open
return ZipExtFile(zef_file, mode, zinfo, pwd, True)
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 797, in __init__
self._decompressor = _get_decompressor(self._compress_type)
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 698, in _get_decompressor
_check_compression(compress_type)
File "/usr/local/Cellar/python@3.9/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/zipfile.py", line 678, in _check_compression
raise NotImplementedError("That compression method is not supported")
NotImplementedError: That compression method is not supported
ファイルコマンドで調べてみると
同じzipファイルでもバージョンが違うことが分かりました
$ file *.zip
test.zip: Zip archive data, at least v2.0 to extract
test_aes.zip: Zip archive data, at least v4.5 to extract
.ZIPフォーマット
普段はあまり意識することはありませんが
.ZIPフォーマットにはこちらに記載されている通り複数のバージョンが存在します
またフォーマットごとに差異もあります
過去開かれていたpythonのissueを確認すると以下のようなコメントがありました
zipfile only supports the "Traditional PKWARE Encryption" method.
Support for other encryption methods would be useful.
暗号化方式を確認すると解凍出来なかったファイルはAES方式で暗号化されていたものでした
pythonでAES方式で暗号化されたzipファイルを解凍する
このファイルを解凍する際、手作業で解凍するだけならそこまで難しいものではありません
よく使われる7-Zipを使用すれば通常の.zipファイルと同様に解凍出来ます
今回はp7zipをインストールして実行してみます
$ 7z x test_aes.zip
7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28
p7zip Version 17.04 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,4 CPUs x64)
Scanning the drive for archives:
1 file, 552423218 bytes (527 MiB)
Extracting archive: test_aes.zip
--
Path = test_aes.zip
Type = zip
Physical Size = 552423218
Enter password (will not be echoed):
Everything is Ok
Size: 4452467111
Compressed: 552423218
これをsubprocessで呼び出せば以下の通り解凍自体は出来ます
>>> import subprocess
>>> subprocess.call(["7z", "x", "-ptestpass", "test_aes.zip"])
7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28
p7zip Version 17.04 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,4 CPUs x64)
Scanning the drive for archives:
1 file, 552423218 bytes (527 MiB)
Extracting archive: test_aes.zip
--
Path = test_aes.zip
Type = zip
Physical Size = 552423218
Everything is Ok
Size: 4452467111
Compressed: 552423218
0
しかし、これではWebアプリケーション上で毎回コマンド実行している状態でしたので
解凍可能なライブラリを探してみたところpyzipperというライブラリを見つけたのでこちらを用いて解凍してみます
>>> import pyzipper
>>> with pyzipper.AESZipFile('test_aes.zip') as f:
... f.extractall('dist/', pwd=b'testpass')
...
>>>
実行結果は
$ ls dist/
test_aes.csv
となり想定通りに解凍出来ていることが確認できました
zipfileモジュールを呼び出す時と同じ呼び出し方で解凍出来るので
分かりやすいと個人的には感じています
結び
以上、zipfileコマンドで解凍出来ない.zipファイルを解凍してみました
困ったらsubprocessモジュール使ってコマンド実行で何とかしていたのですが
使いやすいライブラリを探してみることも大事だなぁと改めて気づかされました
最後までお付き合い頂きありがとうございました
参考
https://bi.biopapyrus.jp/os/linux/compress.html
https://gist.github.com/ysakasin/2edf8d3bf55c6ebf63f82851e302b030
https://stackoverflow.com/questions/15553150/python-unzip-aes-128-encrypted-file
https://so-zou.jp/software/tech/security/encryption/algorithms.htm