파이썬으로 폴더 파일 무결성(SHA-256) 자동 점검하기

백업 디스크나 NAS에 쌓아 둔 자료는 평소에 열어 보지 않기 때문에, 파일이 조용히 깨지거나(비트 로트) 누가 실수로 지워도 한참 뒤에야 알게 됩니다. 파일마다 SHA-256 해시를 기록해 두고 주기적으로 비교하면 이런 변화를 바로 잡아낼 수 있습니다. 표준 라이브러리만 쓰는 스크립트라 파이썬 3.8 이상이면 추가 설치 없이 동작합니다.

1. 동작 방식

  • 첫 실행: 폴더 안 모든 파일의 크기, 수정 시각, SHA-256을 manifest.json에 저장 (기준점)
  • 이후 실행: 다시 계산해서 기준과 비교하고 4가지로 분류해 보고
분류판단 기준의미
missing기준에는 있는데 지금 없음삭제·이동됨
added지금 새로 생김새 파일
changed해시와 수정 시각이 모두 바뀜정상적인 편집일 가능성 높음
corrupted수정 시각은 그대로인데 해시만 바뀜손상 의심, 즉시 확인 필요

2. 전체 코드

"""폴더 무결성 점검: 첫 실행은 기준(manifest) 생성, 이후 실행은 변경/누락/신규 파일 보고."""
import hashlib, json, sys, time
from pathlib import Path

CHUNK = 1024 * 1024  # 1MB씩 읽어 대용량 파일도 메모리 부담 없음

def sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        while block := f.read(CHUNK):
            h.update(block)
    return h.hexdigest()

def scan(root: Path) -> dict:
    result = {}
    for p in root.rglob("*"):
        if p.is_file():
            st = p.stat()
            result[str(p.relative_to(root))] = {"size": st.st_size, "mtime": int(st.st_mtime), "sha256": sha256(p)}
    return result

def main(root: str, manifest: str = "manifest.json"):
    root_p, man_p = Path(root), Path(manifest)
    now = scan(root_p)
    if not man_p.exists():
        man_p.write_text(json.dumps(now, ensure_ascii=False, indent=1), encoding="utf-8")
        print(f"기준 생성: 파일 {len(now)}개 → {man_p}")
        return 0
    base = json.loads(man_p.read_text(encoding="utf-8"))
    missing = sorted(set(base) - set(now))
    added = sorted(set(now) - set(base))
    changed, corrupted = [], []
    for k in set(base) & set(now):
        if base[k]["sha256"] != now[k]["sha256"]:
            # 수정 시각이 그대로인데 내용만 바뀌었다면 손상(비트 로트) 의심
            (corrupted if base[k]["mtime"] == now[k]["mtime"] else changed).append(k)
    report = {"time": time.strftime("%Y-%m-%d %H:%M:%S"), "missing": missing, "added": added,
              "changed": sorted(changed), "corrupted": sorted(corrupted)}
    print(json.dumps(report, ensure_ascii=False, indent=1))
    return 1 if (missing or corrupted) else 0

if __name__ == "__main__":
    sys.exit(main(*sys.argv[1:]))

파일을 1MB씩 나눠 읽기 때문에 수십 GB짜리 영상 파일도 메모리를 거의 쓰지 않습니다. 누락이나 손상 의심이 있으면 종료 코드 1을 돌려줘서, 다른 스크립트나 스케줄러에서 실패로 감지할 수 있습니다.

3. 실행 방법

# 첫 실행: 기준 생성 (manifest는 점검 대상 폴더 밖에 두기)
python integrity_check.py "D:\Backup\Photos" "C:\check\photos_manifest.json"

# 다음부터는 같은 명령으로 비교 결과 출력
python integrity_check.py "D:\Backup\Photos" "C:\check\photos_manifest.json"

manifest 파일을 점검 폴더 안에 두면 그 파일 자체가 매번 “변경”으로 잡히므로 반드시 바깥에 둡니다. 정상적으로 파일을 추가·수정한 뒤에는 manifest를 지우고 다시 실행해 기준을 새로 만듭니다.

4. 매일 자동 실행

윈도우 작업 스케줄러: 관리자 명령 프롬프트에서 등록합니다. 결과는 로그 파일에 쌓이도록 합니다.

schtasks /Create /TN "IntegrityCheck" /SC DAILY /ST 03:00 /TR "cmd /c python C:\check\integrity_check.py D:\Backup\Photos C:\check\photos_manifest.json >> C:\check\integrity.log 2>&1"

리눅스·NAS(cron): crontab -e에 추가합니다.

0 3 * * * /usr/bin/python3 /opt/check/integrity_check.py /volume1/photos /opt/check/photos_manifest.json >> /opt/check/integrity.log 2>&1

5. 활용 팁

  • 대용량 폴더: HDD 기준 대략 100~200MB/s로 해시를 계산하므로 수 TB라면 몇 시간이 걸립니다. 폴더를 나눠 요일별로 점검하면 부담이 줄어듭니다.
  • 알림: 종료 코드가 1일 때만 메일이나 메신저 웹훅을 보내도록 감싸면, 문제가 있을 때만 알림을 받을 수 있습니다.
  • 원본·백업 비교: 원본 폴더로 만든 manifest를 백업 폴더 점검에 그대로 쓰면, 백업이 원본과 똑같이 복사됐는지 검증할 수 있습니다.

댓글 남기기