LoginSignup
8
0

More than 1 year has passed since last update.

Unzip .zip that cannot unzip with zipfile

Last updated at Posted at 2021-12-05

この記事はBrainPad Advent Calendar 6日目の記事になります

こんにちは@nissy0409240です
BrainPadでエンジニアをしています

2021年もあと一ヶ月を切りましたが
皆様いかがお過ごしでしょうか

先日ですが休日に博物館へ行きました
image.png

道中のコンビニで買った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

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0