ファイルのハッシュ値(チェックサム)を計算すると、ファイルの整合性確認や重複検出ができる。Python の hashlib モジュールを使う。
MD5 ハッシュを計算する
import hashlib
def md5_hash(filepath):
hash_md5 = hashlib.md5()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
print(md5_hash('data.txt')) # 'd41d8cd98f00b204e9800998ecf8427e'
チャンク単位で読み込むことで、大容量ファイルでもメモリを節約できる。
SHA256 ハッシュを計算する
MD5 は衝突攻撃に対して脆弱なため、セキュリティが重要な場面では SHA256 を使う。
import hashlib
def sha256_hash(filepath):
hash_sha256 = hashlib.sha256()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
hash_sha256.update(chunk)
return hash_sha256.hexdigest()
print(sha256_hash('data.txt'))
汎用的なハッシュ関数
アルゴリズムを引数で指定できる汎用関数を作ると便利だ。
import hashlib
def file_hash(filepath, algorithm='sha256'):
h = hashlib.new(algorithm)
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
h.update(chunk)
return h.hexdigest()
# 使用例
print(file_hash('data.txt', 'md5'))
print(file_hash('data.txt', 'sha256'))
print(file_hash('data.txt', 'sha512'))
利用可能なアルゴリズムは hashlib.algorithms_available で確認できる。
小さなファイルは一度に読む
数 MB 以下の小さなファイルなら、一度に読み込んでも問題ない。
import hashlib
from pathlib import Path
def quick_hash(filepath):
data = Path(filepath).read_bytes()
return hashlib.sha256(data).hexdigest()
ファイルの整合性を確認する
ダウンロードしたファイルのハッシュ値と公開されている値を比較する。
def verify_file(filepath, expected_hash, algorithm='sha256'):
actual_hash = file_hash(filepath, algorithm)
return actual_hash == expected_hash.lower()
# 使用例
expected = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
if verify_file('download.zip', expected):
print('ファイルは正常です')
else:
print('ファイルが破損または改ざんされています')
重複ファイルを検出する
ハッシュ値を使ってディレクトリ内の重複ファイルを見つける。
from collections import defaultdict
from pathlib import Path
def find_duplicates(directory):
hash_map = defaultdict(list)
for filepath in Path(directory).rglob('*'):
if filepath.is_file():
h = file_hash(filepath)
hash_map[h].append(filepath)
# 重複があるもの(同じハッシュが2つ以上)だけを返す
return {h: files for h, files in hash_map.items() if len(files) > 1}
# 使用例
duplicates = find_duplicates('./documents')
for hash_val, files in duplicates.items():
print(f'重複: {files}')
高速化:最初にサイズで絞り込む
大量のファイルを処理する場合、まずファイルサイズで絞り込むと高速化できる。
from collections import defaultdict
from pathlib import Path
def find_duplicates_fast(directory):
# まずサイズでグループ化
size_map = defaultdict(list)
for filepath in Path(directory).rglob('*'):
if filepath.is_file():
size_map[filepath.stat().st_size].append(filepath)
# サイズが同じファイルだけハッシュを計算
hash_map = defaultdict(list)
for size, files in size_map.items():
if len(files) > 1:
for filepath in files:
h = file_hash(filepath)
hash_map[h].append(filepath)
return {h: files for h, files in hash_map.items() if len(files) > 1}
サイズが異なるファイルは絶対に重複しないため、ハッシュ計算を省略できる。