본문으로 건너뛰기
AZ Tools

gzip(.gz) 구조 분석기

.gz는 아카이브가 아니라 스트림입니다. 멤버 하나 이상이 그대로 이어 붙은 형태이고, 멤버마다 10바이트 헤더와 deflate 스트림, 그리고 CRC32와 ISIZE(2^32로 나눈 나머지 크기)를 담은 8바이트 트레일러를 따로 가집니다. 이 페이지는 그 멤버를 전부 걸어가며 각 필드가 실제로 무엇을 말하는지 보여 줍니다. 1f 8b 매직과 압축 방식, FLG의 다섯 비트(FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT)와 그 비트가 약속한 선택 필드, 날짜로 환산한 MTIME — 0은 설정하지 않았다는 뜻이고 재현 가능한 빌드가 쓰는 값입니다 —, XFL(2는 최대 압축, 4는 최고 속도), 이름으로 풀어 준 OS 바이트, 두 바이트 ID와 페이로드로 나눈 FEXTRA 서브필드, 원본 파일 이름, 주석, 헤더 CRC16까지입니다. 그다음 검증합니다. 각 멤버를 브라우저가 직접 풀고 그 결과로 CRC32를 다시 계산하므로, 트레일러를 그대로 옮겨 적는 것이 아니라 실제로 대조합니다. CRC가 맞지 않는 파일은 보통 복원 도중에야 정체를 드러내는데, 그때는 이미 원본이 없는 경우가 많습니다. 같은 과정에서 실제 원본 크기도 나오는데, 이것이 gzip -l의 숫자와 달라질 수 있는 이유입니다. gzip -l은 마지막 멤버의 ISIZE를 읽어 파일 전체 크기라고 출력하므로, cat a.gz b.gz로 만든 파일이나 병렬 압축기가 만든 파일에서는 앞쪽 멤버가 담은 양만큼 적게 나옵니다. ISIZE는 32비트라서 4GB가 넘는 멤버는 나머지만 저장하고, 그 필드만으로는 실제 크기를 알 수 없습니다. 마지막 트레일러 뒤의 쓰레기 바이트, 중간에서 끊긴 멤버, 아예 gzip이 아닌 파일은 각각 이름을 붙여 알려 주고 어중간하게 해석하지 않습니다. 멤버 안의 파일 이름 — gunzip -N이 복원 대상으로 삼는 이름이고, 경로일 수도 지금 파일과 전혀 다른 이름일 수도 있습니다 — 은 저장된 그대로 보여 줍니다. 규격상 ISO-8859-1이지만 실제로는 로캘 바이트가 들어가므로, 해석이 갈리면 두 가지를 모두 표시합니다.

사용법

  1. .gz, .tgz, 그 밖의 gzip 스트림을 상자에 놓으세요. 페이지 안에서 파싱하고 압축을 풀며 업로드하지 않습니다.
  2. 요약부터 보세요. 파일에 멤버가 실제로 몇 개인지, 진짜 원본 크기 합계는 얼마인지, CRC32가 모두 맞았는지 알 수 있습니다.
  3. 멤버를 펼쳐 헤더를 확인하세요. 플래그 다섯 비트, MTIME, XFL, OS 바이트, 저장된 파일 이름, 주석, FEXTRA 서브필드가 나옵니다.
  4. 트레일러의 두 쌍을 비교하세요. 트레일러의 CRC32와 데이터의 실제 CRC32, ISIZE와 실제 원본 크기입니다. 둘 다 일치하는 것이 gzip -t가 확인하는 내용입니다.
  5. 멤버가 여러 개인 파일이라면, gzip -l 숫자를 인용하기 전에 그 값과 실제 합계의 차이를 확인하세요.

자주 묻는 질문

gzip -l이 이 페이지와 다른 크기를 보여 주는 이유는 무엇인가요?
gzip -l은 압축을 전혀 풀지 않기 때문입니다. 파일 끝의 4바이트로 건너뛰어 그 ISIZE를 읽고 원본 크기라고 출력합니다. 멤버가 하나뿐이면 맞는 값입니다. 그러나 멤버를 이어 붙여 만든 파일 — cat a.gz b.gz, 아카이브 뒤에 덧붙인 로그, 독립 블록을 쓰는 pigz나 bgzip — 에서는 그 4바이트가 마지막 멤버만 설명하므로 앞선 모든 멤버의 양이 통째로 빠집니다. 이 페이지는 대신 모든 멤버를 풀어 실제로 나온 바이트를 더하기 때문에 훨씬 큰 합계가 나올 수 있습니다. 대신 gzip -l은 즉시 끝나고 이 페이지는 그렇지 않습니다. 정직한 답의 비용이 곧 압축을 푸는 시간입니다.
MTIME이 0이면 무슨 뜻이고, 이 값은 어떤 시각인가요?
MTIME은 원본 파일의 수정 시각을 UTC 기준 유닉스 에폭 이후 초로 담습니다. 압축을 실행한 시각도, 디스크에 있는 .gz 파일의 타임스탬프도 아닙니다. 0은 시각을 알 수 없었거나 저장하지 않았다는 뜻입니다. gzip -n은 일부러 0을 쓰고, 재현 가능한 빌드도 마찬가지입니다. 같은 내용을 빌드했는데 바이트가 달라지는 대표적 원인이 타임스탬프이기 때문입니다. 파이프에서 압축할 때도 시각을 가져올 파일이 없으니 0이 됩니다. 필드는 4바이트라서 2106년 이후는 아예 표현할 수 없고, 2^31을 넘는 값을 먼 미래로 볼지 음수로 볼지는 구현마다 다릅니다.
원본 파일 이름이 깨져 보입니다. 파일이 손상된 건가요?
아닙니다. 규격이 그런 것입니다. RFC 1952는 FNAME을 ISO-8859-1 문자열로 정의하지만, 리눅스와 macOS의 gzip은 로컬 인코딩이 만들어 낸 이름 바이트를 그대로 씁니다. 요즘은 대부분 UTF-8입니다. 그래서 ASCII를 벗어난 이름은 같은 인코딩을 쓰는 시스템끼리만 온전히 오갑니다. UTF-8 시스템에서 적은 이름을 Latin-1로 읽으면 흔히 보는 깨진 글자가 나옵니다. 이 페이지는 규격대로 디코딩한 값을 보여 주고, 같은 바이트가 유효한 UTF-8이면서 다르게 읽힐 때는 UTF-8 해석을 나란히 보여 줍니다. 두 후보를 모두 보고 어느 쪽이 원래 이름인지 판단할 수 있습니다.
CRC32 불일치는 무엇을 증명하고, 일치는 무엇을 증명하지 못하나요?
불일치는 deflate 스트림에서 나온 바이트가 압축 당시 들어간 바이트와 다르다는 뜻입니다. 저장 매체에서 뒤집힌 비트, 잘렸다가 이어 붙은 전송, 압축 데이터에 가한 수정 같은 것들입니다. gunzip이 "invalid compressed data — crc error"로 알려 주는 바로 그 실패이며, 다만 여기서는 출력 파일을 어디에도 쓰지 않고 확인합니다. 반대로 일치는 데이터가 압축기가 본 그대로라는 것만 증명합니다. CRC32는 32비트 오류 검출 부호일 뿐 서명이 아닙니다. 우연한 손상은 아주 높은 확률로 잡아내지만, 데이터를 바꿀 수 있는 사람은 CRC도 다시 계산할 수 있습니다. 진위 확인에는 트레일러가 아니라 파일 전체에 대한 해시나 서명이 필요합니다.
.tar.gz 안의 파일 목록도 볼 수 있나요?
볼 수 없고, gzip만 읽는 어떤 도구도 볼 수 없습니다. gzip은 바이트 스트림 하나를 압축할 뿐 그 안의 내용은 모릅니다. 목차도, 파일별 항목도 없고, 앞부분을 다 풀지 않고 파일 하나만 꺼낼 방법도 없습니다. .tar.gz는 그런 구조를 가진 tar 아카이브를 통째로 gzip에 통과시킨 것입니다. 이 페이지가 알려 줄 수 있는 것은 gzip 계층이 기록한 내용뿐입니다. 멤버가 몇 개인지, 각 헤더의 원본 파일 이름(보통 archive.tar), 타임스탬프, 그리고 나오는 데이터의 양입니다. 항목 목록을 보려면 압축을 푼 스트림을 읽는 tar 리더가 필요합니다.
FEXTRA 서브필드는 어디에 쓰이나요?
FEXTRA는 확장 지점입니다. 길이가 앞에 붙은 서브필드가 이어지고, 각 서브필드는 두 바이트 ID와 자체 길이, 페이로드로 이루어집니다. ID를 모르는 리더는 건너뛰어야 합니다. 실제 용도는 대부분 gzip 스트림을 탐색 가능하게 만드는 것입니다. bgzip과 유전체학의 모든 BAM 파일 뒤에 있는 BGZF는 "BC" 서브필드에 각 블록 크기를 적어 두어 블록 경계로 바로 건너뛸 수 있게 하고, dictzip은 같은 이유로 "RA" 아래에 청크 길이 표를 저장합니다. 둘 다 여전히 gunzip이 정상적으로 읽는 평범한 gzip 파일이라는 점이 이 장치의 핵심입니다. 낯선 서브필드가 보인다면 그런 도구가 만든 파일이라는 단서입니다.

관련 도구