ZIP 파일을 두 번 클릭해 목록이 보이면 백업은 성공한 것처럼 느껴집니다. 하지만 파일이 빠졌거나 다른 내용으로 바뀌어도 압축 파일 자체는 정상일 수 있습니다. 백업 검수는 압축 구조가 읽히는지, 예상한 경로가 모두 있는지, 각 파일의 바이트와 해시가 맞는지, 실제로 복원되는지를 나눠야 합니다. 이 글은 작은 가상 ZIP을 메모리에서 만들고 새 임시 폴더에 복원해 확인했습니다. 업무 폴더나 고객 자료를 읽지 않았으며 모든 종류의 시스템 복구를 보장하는 도구도 아닙니다.

백업 전에 예상 목록부터 고정합니다.

가상 원본은 catalog/summary.txt, inventory/BK-42.tsv, flags/catalog-ready.flag 세 개입니다. catalog/summary.txt에는 도서 묶음 BK-42와 수량 7의 요약 두 줄, inventory/BK-42.tsv에는 같은 도서의 식별자와 수량을 탭 구분으로 넣었습니다. flags/catalog-ready.flag는 내용이 없는 준비 표식입니다. 빈 파일도 원본의 일부라는 점을 확인하기 위한 구성입니다. 각 경로마다 바이트 수와 SHA-256을 기록한 expectedManifest를 먼저 만들고 그 후 압축 결과와 비교했습니다. 압축된 ZIP을 보고 뒤늦게 목록을 만들면 누락된 파일을 원래 없었다고 받아들일 수 있습니다.

목록의 기준 시점도 중요합니다. 원본이 계속 바뀌는 동안 한 파일씩 읽으면 서로 다른 시점의 상태를 섞은 백업이 될 수 있습니다. 데이터베이스처럼 일관성이 필요한 서비스는 별도의 정지·스냅샷·내보내기 절차가 필요합니다. 이 예제는 바뀌지 않는 세 개의 고정 바이트 배열만 다루므로 그런 문제를 시험하지 않았습니다. 파일 시스템에서 쓰려면 상대 경로, 길이, 해시, 오류와 읽은 시각을 함께 남기고 실패한 파일을 조용히 건너뛰지 않는 명세가 필요합니다.

ZIP의 CRC 검사와 원본 일치는 다른 질문입니다.

Python zipfile의 testzip은 항목을 읽으며 CRC와 파일 헤더 문제를 검사하고 문제가 없으면 None을 반환합니다. 정상 가상 ZIP은 그 검사를 통과했습니다. 그러나 catalog/summary.txt의 collection을 COLLECTION으로 바꾼 뒤 새 ZIP을 만들었을 때도 testzip은 None입니다. 새 내용에 맞는 압축 구조와 CRC가 저장됐기 때문입니다. 이 파일은 손상된 ZIP이 아니라 원본과 다른 내용이 정상적으로 압축된 ZIP입니다.

원본의 예상 SHA-256과 비교하면 changed 목록에 catalog/summary.txt가 나타납니다. 두 버전은 이 예제에서 길이도 같으므로 크기 비교만으로는 찾을 수 없습니다. 그래서 구조 검사, 목록 대조, 길이·내용 해시를 별도 결과로 반환합니다. ‘압축이 풀린다’는 사실을 ‘내가 보존하려던 내용과 같다’로 확대하지 마세요. 반대로 해시가 다르다는 이유만으로 악성 변조라고 단정하지도 않습니다. 의도한 편집이나 줄바꿈 변경일 수 있으므로 차이의 원인은 원시 기록으로 확인해야 합니다.

이 사례에서 정상 catalog/summary.txt와 바뀐 catalog/summary.txt의 길이는 모두 28바이트입니다. 이름·길이·파일 수·전체 길이가 같고 ZIP CRC 검사도 정상인데 내용 해시만 달라집니다. 따라서 검수 조건을 하나씩 추가했을 때 어떤 반례가 걸리는지 확인할 수 있습니다. 반면 빈 파일 누락은 해시가 있는 기존 항목만 반복 검사하는 방식으로는 찾지 못하고 예상 경로 집합을 대조해야 발견됩니다. 두 반례는 서로 다른 종류의 누락입니다.

복원한 파일이 읽히지 않는다면 해시 대조 전에 read 오류를 별도 상태로 기록해야 합니다. 읽지 못한 것을 빈 파일 해시와 비교하거나 파일이 없다는 이유로 예상 목록에서 제거하면 결과가 정상처럼 보일 수 있습니다. 이 코드에서는 가상 파일 세 개를 모두 읽었지만, 운영용 구현이라면 읽기 오류·경로 충돌·복원 중단을 실패 상태로 반환하고 다음 단계를 멈추는 계약을 추가해야 합니다.

빈 파일 하나가 빠져도 누락으로 기록합니다.

세 번째 ZIP에서는 flags/catalog-ready.flag를 빼고 나머지만 넣었습니다. 압축 자체는 정상이며 나머지 파일 내용도 맞습니다. 예상 경로 집합에서 실제 경로 집합을 빼면 missing에 flags/catalog-ready.flag가 나옵니다. 크기가 0이라 총 바이트 합계는 변하지 않지만 파일 목록은 다릅니다. 설정이나 준비 상태를 나타내는 빈 파일처럼 내용이 없어도 의미가 있는 항목을 합계만으로 놓칠 수 있습니다.

반대로 예상에 없던 경로가 추가되는 경우도 실제로 실행했습니다. 정상 세 파일에 scratch/work.tmp를 하나 더 넣은 별도 ZIP에서는 구조 검사가 정상이고 기존 세 파일의 내용도 맞았지만 unexpected에 이 경로가 남았습니다. 누락만 검사해서는 작업 중 섞여 들어간 임시 파일을 놓칠 수 있습니다. 누락·추가·내용 변경을 한 숫자의 실패 개수로만 줄이지 않고 해당 경로를 제공하면 복구 전에 원인을 좁힐 수 있습니다. 디렉터리 항목을 독립적으로 기록할지, 비어 있는 디렉터리를 보존할지도 정해야 합니다. 이 실험은 파일 세 개의 상대 경로만 목록으로 삼았고 디렉터리 권한이나 빈 디렉터리 자체의 복원은 검증하지 않았습니다. 업무 계약에 필요하면 목록 형식을 그 범위만큼 확장해야 합니다.

실행 입력ZIP CRC 검사원본 대조
정상 세 파일이상 없음일치
catalog/summary.txt 대문자 변경이상 없음catalog/summary.txt 내용 변경
빈 파일 제외이상 없음flags/catalog-ready.flag 누락
scratch/work.tmp 추가이상 없음unexpected에 추가 경로
중복 catalog/summary.txt구조는 읽힘duplicateNames=true
../escape.txt구조는 읽힘위험 경로·추출 안 함

중복 경로와 위험한 경로는 먼저 분리합니다.

ZIP에는 같은 이름의 항목이 여러 번 들어갈 수 있습니다. 경로를 사전 키로만 바꾸면 나중 값이 앞 값을 가려 버릴 수 있으므로 코드에서 namelist의 전체 길이와 고유 이름 수를 비교합니다. 같은 catalog/summary.txt 이름에 one과 two를 넣은 별도 ZIP도 실제로 만들었고 duplicateNames가 true인 것을 확인했습니다. 이 ZIP은 복원하지 않고 이름 중복으로 따로 남겼습니다. 어떤 항목을 선택할지 임의로 정해 정상 백업으로 처리하지 않습니다.

../escape.txt라는 가상 항목도 따로 만들었습니다. 코드는 이를 unsafeNames에 기록하고 이 ZIP은 디스크에 풀지 않았습니다. 절대 경로, 상위 이동과 역슬래시를 우선 거부하는 작은 검사입니다. 이는 알려지지 않은 모든 압축 파일을 안전하게 푸는 보안 제품이 아닙니다. 심볼릭 링크, 플랫폼별 경로 규칙, 압축 폭탄과 자원 제한 등 추가 위험이 있습니다. 공식 zipfile 문서의 주의사항을 읽고 신뢰할 수 없는 파일은 내용 검사와 격리 정책을 별도로 적용해야 합니다.

복원 검사는 새 위치에서 실제 내용을 다시 읽습니다.

정상이라고 예상한 고정 ZIP만 새로운 임시 폴더에 extractall로 풀었습니다. 파일 세 개를 다시 열어 SHA-256을 계산하고 예상 해시와 비교한 결과 restoredContentMatches는 true였습니다. 원본 폴더를 덮어쓰지 않았으며 임시 폴더는 작업 종료 후 정리했습니다. 이 결과가 증명하는 것은 이 환경에서 이 세 파일의 바이트가 새 위치에 복원됐다는 사실입니다.

운영 백업이라면 압축을 푸는 데서 끝나지 않습니다. 애플리케이션이 복원된 설정을 읽는지, 필요한 권한과 소유자·실행 비트·확장 속성이 살아 있는지, 암호 키와 외부 의존 자료가 있는지도 확인해야 합니다. 여기에는 그런 항목이 없습니다. “복구 테스트 완료”라는 표현을 쓰려면 어떤 파일·환경·기능을 검증했는지 뒤에 붙이세요. 실제 서비스가 올라왔다는 결과와 단순한 바이트 복원은 다른 층위의 증거이며 둘을 같은 통과 표시로 합치지 않는 편이 좋습니다.

해시가 맞아도 출처의 신뢰를 대신하지 못합니다.

SHA-256은 같은 알고리즘에서 내용이 달라졌는지 대조하기 위한 값입니다. 목록 자체가 잘못됐거나 ZIP과 목록이 함께 바뀌었다면 두 해시가 맞는다는 사실만으로 올바른 원본에서 온 자료임을 입증하지 못합니다. 그래서 expectedManifest를 언제 누구의 어떤 원본에서 만들었는지와 별도로 보존하는 경로가 필요합니다. 이 글은 서명과 인증된 배포 체계를 구현하지 않았습니다.

원본을 감염된 상태에서 백업한 경우에도 해시는 완벽히 일치할 수 있습니다. 내용이 업무적으로 올바른지, 개인정보가 포함됐는지, 파일이 실행 가능한 위험을 갖는지 같은 문제는 해시 비교의 범위 밖입니다. 복구 지점을 선택할 때도 최신 파일이라는 이유만으로 충분하지 않습니다. 원하는 상태로 돌아갈 수 있는지 기능과 기간 조건을 함께 봅니다. 이 구분은 JSON이 파싱된다는 이유로 업무 내용이 맞다고 판정하지 않는 것과 같습니다. 구조 통과와 목적 적합성을 별도 행으로 기록하세요.

자동화 결과표에는 실패를 숨기지 않는 항목이 필요합니다.

예상 파일 수, 실제 항목 수, 중복 경로 여부, unsafe 목록, 누락·추가·변경 경로, 구조 검사 오류와 복원 결과를 각각 둡니다. 모든 값이 비어 있을 때만 다음 단계로 가도록 설계하되, 읽기 권한 오류나 용량 제한 같은 검사 불가 상태도 통과와 구분합니다. 압축 파일이 너무 커 일부만 읽었다면 전체를 검사했다고 적을 수 없습니다. 이 예제는 작은 메모리 자료를 전부 읽었으므로 전체 항목의 바이트 대조가 가능합니다.

파일 목록의 순서나 ZIP 컨테이너 자체의 해시가 다르더라도 내부 파일의 경로·내용이 같을 수 있습니다. 압축 시각이나 압축 설정이 달라지면 컨테이너 바이트가 바뀔 수 있기 때문입니다. 다운로드 코드는 그런 ZIP 바이트의 재현성을 주장하지 않고 항목별 원본 내용을 비교합니다. 전달 파일이 한 비트도 바뀌지 않았는지 확인하려는 작업이라면 컨테이너 해시도 별도로 필요합니다. 무엇을 동일하게 유지해야 하는지에 따라 두 검사가 서로 보완합니다.

백업 점검은 작고 복원 가능한 범위부터 시작합니다.

독자는 업무 원본 대신 가상 텍스트 세 개로 먼저 코드를 실행해 정상, 동일 길이의 내용 변경, 빈 표식 누락, 예상 밖 파일 추가와 위험 경로 결과를 확인할 수 있습니다. 그 후 실제 적용 명세를 만들 때는 원본 범위와 제외 항목을 문서화하고, 별도 복원 위치와 되돌리기 조건부터 정합니다. 이 실험 코드를 임의의 ZIP에 바로 연결하거나 기존 폴더에 덮어쓰는 도구로 바꾸는 것은 검증 범위를 벗어납니다.

보고서 문장은 “정상 ZIP의 세 항목을 새 임시 폴더에 풀어 내용 해시 일치를 확인했고, 바뀐 내용과 빈 파일 누락은 따로 탐지했다” 정도가 정확합니다. 데이터베이스·권한·서비스 재시작·재해 복구는 미확인으로 남깁니다. 파일 이름 변경 실패 후 해시를 되돌리는 글과 ID별 원본 대조 글을 연결해 읽으면, 자동 작업 전에 보존할 정보와 작업 후 다시 확인할 정보가 어떻게 다른지 이해하기 쉽습니다. 백업이라는 이름보다 실제 복원 가능한 범위를 먼저 보여 주세요.

입력과 판정을 다시 확인하려면

이 글의 가상 입력과 코드는 2026년 10월 15일 로컬 Python 3에서 실행했습니다. 글의 발행일과 실행일은 다릅니다. 별도 설치 패키지 없이 Python 표준 라이브러리로 실행할 수 있습니다.

터미널에서 python3 archive-manifest-integrity-20261015.py를 실행하면 JSON을 출력합니다. 사용자 자료나 비밀번호는 필요하지 않습니다. 입력과 실행 환경은 결과 JSON에 함께 남겼습니다.

계산 기준을 확인한 원문과 다음 작업

공식 원문은 2026-10-15에 확인했습니다. 문서가 설명하는 API·정의와 이 글의 가상 계산 결과는 구분해서 읽어 주세요.

함께 보면 좋은 글: 파일 변경 중 실패 복원, ID별 원본 대조.