zipファイルの破損チェックが役立つ場面
自動ダウンロード時のデータ欠落対策
外部でのデータを自動でダウンロードし、そのデータをシステムに取り込むことがあると思います。ダウンロード時に何らかの理由でzipファイルが破損した場合、データを取り込む前にチェックを行えば中途半端なデータや書き換えられたデータを取り込んでしまうことを防げます。
ユーザがアップロードしたファイルの安全性確保
ユーザ入力のzipファイルが問題ないかの検証に使用できます。
Pythonでのzipファイルの破損チェック方法
使用する関数
どちらもPython標準ライブラリであるzipfileの関数です。
zipfile.is_zipfile(filename)
filename が正しいマジックナンバをもつ ZIP ファイルの時に True を返し、そうでない場合 False を返します。filename にはファイルやファイルライクオブジェクトを渡すこともできます。
以下を検出できます。
- zip形式でないファイル
- 0Byteのzipファイル
メリット
- 解凍しないでチェックを行うため処理速度が速い
デメリット
- zipの形をしているかという外側しかチェックできない
ZipFile.testzip()
アーカイブ内のすべてのファイルを読み込み、それらのCRC(巡回冗長検査)とファイルヘッダーをチェックします。破損している最初のファイルの名称を返します。問題がなければ None を返します。
以下を検出できます。
- 圧縮されたデータの一部が書き換わっている
- 圧縮データ内の一部のファイルが欠落している
メリット
- 実際にデータをすべて解凍してデータの整合性チェックを行うため見逃しが少ない
デメリット
- 解凍を行うため処理に時間がかかる
実際に検証
前提条件
- Python3.13
サンプルコード
check_zip_file.py
import os
import zipfile
def check_zip_file(file_path):
"""指定されたZIPファイルの破損チェックを行う関数"""
# 正しいZIP形式かどうかの事前チェック
if not zipfile.is_zipfile(file_path):
print(f"Error: このファイルは有効なZIP形式ではないか、完全に破損しています。")
return False
try:
# ZIPファイルを開く
with zipfile.ZipFile(file_path, "r") as zf:
# testzip() は破損した最初のファイルの名称を返す(問題なければ None を返す)
bad_file = zf.testzip()
if bad_file is not None:
print(f"Warning: ZIPファイルが破損しています。")
print(f" 最初にエラーが検出された内部ファイル: {bad_file}")
return False
else:
print(f"Success: ZIPファイルの破損はありません。")
return True
except zipfile.BadZipFile:
print("Error: ZIPファイル構造が致命的に破損しています。")
return False
except Exception as e:
print(f"Error: 予期せぬエラーが発生しました: {e}")
return False
if __name__ == "__main__":
target_zip = "example.zip"
result = check_zip_file(target_zip)
if result:
print("zipファイル破損チェック結果:ZIPファイルは正常です。")
else:
print("zipファイル破損チェック結果:破損が検出されました。")
サンプルコード実行結果
| 項番 | 対象ファイル | 実行結果 | 破損・異常の検出箇所 |
|---|---|---|---|
| 1 | 0Byteのzipファイル | Error: このファイルは有効なZIP形式ではないか、完全に破損しています。 |
is_zipfile()の段階でエラーを検出 |
| 2 | 別形式を.zipに偽装したファイル | Error: このファイルは有効なZIP形式ではないか、完全に破損しています。 |
is_zipfile()の段階でエラーを検出 |
| 3 | ヘッダを改ざんしたzipファイル | Warning: ZIPファイルが破損しています。 |
is_zipfile()の段階でエラーを検出 |
| 4 | データの一部を書き換えたファイル | Warning: ZIPファイルが破損しています。 |
testzip()で破損した内部ファイルtest/test1.txtの読み出し(解凍)時にエラーを検出 |
| 5 | パスワード付きzipファイル | Error: 予期せぬエラーが発生しました: File 'test/test1.txt' is encrypted, password required for extraction |
testzip()で暗号化された内部ファイル test/test1.txtの読み出し(解凍)時にエラーを検出 |
補足
以下コードで破損したzipファイルを作成。
ヘッダの改ざん
create_bad_header_zip_file.py
def create_bad_header_zip_file():
with open("normal.zip", "rb") as f:
data = bytearray(f.read())
if len(data) > 4:
data[0:4] = b"FAKE" # 先頭の「PK..」を消し去る
with open("bad_header.zip", "wb") as f:
f.write(data)
データの書き換え
create_bad_ccr_zip_file.py
def create_bad_ccr_zip_file():
with open("normal.zip", "rb") as f:
data = bytearray(f.read())
if len(data) > 100:
data[100] = 0xFF # ファイルの真ん中あたりの1バイトを適当に書き換える
with open("bad_crc.zip", "wb") as f:
f.write(data)
まとめ
実務で使用するタイミングがあったのでまとめてみました。
testzip()は処理に時間はかかりますが、確実に破損チェックを行いたい場合有効だなと思います。