위치가 새는 흔한 경로: 원본 파일을 그대로 제공하는 게시판에 고양이 사진을 올린다. 사진을 찍은 휴대폰이 GPS 좌표를 파일에 기록했고, 게시판은 바이트를 그대로 전달했다. 누구든 이미지를 무료 EXIF 뷰어에 넣으면 좌표를 읽을 수 있다.

EXIF 메타데이터 뷰어 열기 →

EXIF는 디지털 사진의 한 단면이지만, 대부분의 사람은 단 한 번도 들여다본 적이 없다. 이 글에서는 메타데이터 블록에 실제로 무엇이 들어 있는지, 왜 들어가는지, 각 SNS는 업로드 시 무엇을 제거하고 무엇을 남기는지, 그리고 서버로 아무것도 올리지 않은 채 확인하고 제거하는 방법을 정리한다.

EXIF에 실제로 담기는 정보

EXIF(Exchangeable image file format)는 CIPA와 JEITA가 관리하는 표준(현행 Exif 3.0, CIPA DC-008-2023)으로, 카메라와 휴대폰이 이미지 파일에 구조화된 메타데이터를 넣는 방법을 정한다. 1.0판은 1995년에 나왔고, 카메라와 휴대폰이 만든 JPEG에는 기본으로 들어 있다.

휴대폰 카메라로 찍은 일반 사진 한 장에는 시간 도장 외에도 많은 정보가 담긴다:

필드예시기록 이유프라이버시 위험
Make / ModelApple / iPhone 15 Pro카메라 식별낮음 — 수천만 대가 공유
LensModeliPhone 15 Pro back camera 6.86mm f/1.78편집 소프트용 광학 정보낮음
DateTimeOriginal2024-08-12 14:32:08정렬·앨범 정리중 — 언제 어디 있었는지 노출
FNumber / ExposureTime / ISO / FocalLengthf/1.8, 1/120s, ISO 80, 24mm편집 소프트의 노출 재현없음
GPSLatitude / GPSLongitude31.230556° N, 121.473611° E지도·검색의 지오태그높음 — 수 미터 단위 위치 노출
GPSAltitude15 m해발 정보중
SoftwareiOS 18.1, Photoshop 25.4편집 이력낮음~중 — 워크플로 노출
SerialNumber(일부 기종)Sony / Canon DSLR 등기기 개체 식별기자에겐 높은 위험
ImageUniqueID셔터를 누를 때마다 부여되는 UUID중복 제거중

JPEG의 application 마커 세그먼트에는 메타데이터 표준이 두 가지 더 함께 실려 다닌다:

  • XMP(Adobe의 XML 메타데이터) — Lightroom 편집 이력, 평점, 캡션, 저작권 필드를 담는다. EXIF와 같은 APP1 세그먼트, 또는 별도의 APP1에 저장된다.
  • IPTC — 더 오래된 보도실 표준. 캡션, 저자, 저작권을 담으며 APP13 세그먼트에 저장된다.

휴대폰 사진에는 셋 다 함께 있는 경우가 많다: 촬영 매개변수의 EXIF가 한 APP1에, 편집 정보의 XMP가 다른 APP1에, 데스크톱 도구가 찍은 IPTC가 가끔 APP13에 들어간다.

JPEG 레이아웃 — 제거에 재인코딩이 필요 없는 이유

JPEG는 하나의 연속된 바이트 덩어리가 아니다. 각각 0xFF로 시작하는 마커 세그먼트의 흐름이다:

0xFFD8           SOI    Start of Image (이미지 시작)
0xFFE0           APP0   JFIF 해상도와 종횡비
0xFFE1           APP1   EXIF 또는 XMP   ← 개인 데이터가 여기
0xFFE1           APP1   XMP (두 번째도 흔함)
0xFFE2           APP2   ICC 색상 프로파일
0xFFED           APP13  Photoshop / IPTC
0xFFEE           APP14  Adobe 색상 변환 마커
0xFFDB × 2       DQT    양자화 테이블
0xFFC0           SOF    Start of Frame (이미지 크기)
0xFFC4 × 4       DHT    허프만 테이블
0xFFDA           SOS    Start of Scan (압축 픽셀 데이터 시작)
...              엔트로피 코딩된 압축 이미지 데이터
0xFFD9           EOI    End of Image (이미지 끝)

픽셀 데이터는 SOS와 EOI 사이에 있고, SOS 이전은 모두 메타데이터이거나 디코더 설정이다. 메타데이터를 제거하는 데 픽셀을 건드릴 필요가 없다 —— 마커 스트림을 따라 걸으며 개인 데이터를 담은 세그먼트만 떨어뜨리고, 남은 것을 이어 붙이면 끝이다.

ZeroTool 제거기는 세 종류의 마커를 떨어뜨린다:

  • APP1(0xFFE1) — EXIF와 XMP
  • APP13(0xFFED) — Photoshop / IPTC
  • APP14(0xFFEE) — Adobe 색상 변환

남기는 것: SOI, APP0(JFIF), APP2(ICC 색상 프로파일 — 색감 유지), 모든 DQT / SOF / DHT 세그먼트, SOS 마커, 압축 픽셀 데이터, EOI.

결과는 픽셀 측면에서 원본과 바이트 단위로 동일하다. 화질 손실 없음, 재인코딩 없음. 흔히 보는 “Canvas로 다시 그려서 EXIF 제거” 패턴과 비교해 보자:

// 손실 발생 — Canvas로 재인코딩, 화질 떨어짐
canvas.getContext('2d').drawImage(img, 0, 0);
canvas.toBlob(blob => save(blob), 'image/jpeg', 0.95);

Canvas 재인코딩은 한 줄로 끝나고 어느 브라우저에서도 동작하지만, 저장할 때마다 새로운 JPEG 양자화가 일어난다. 세 차례만 왕복해도 가장자리가 눈에 띄게 부드러워지고 평면 색상에 색차 블록이 보인다. 바이트 레벨 방식엔 그런 일이 없다 —— 정리된 파일은 같은 압축 픽셀 그대로이고, 단지 개인 데이터 세그먼트만 잘려 나갔을 뿐이다.

세그먼트를 통째로 지우면 부작용이 두 가지 있다. EXIF Orientation 태그가 APP1과 함께 사라지므로 이 태그에 의존하던 휴대폰 사진은 회전된 채 표시될 수 있다. 또 APP14는 Adobe CMYK / YCCK JPEG를 어떻게 해석할지 디코더에 알려 주므로, 그런 파일은 제거 후 색이 틀어질 수 있다. 휴대폰과 카메라가 찍는 일반 RGB 사진은 APP14에 의존하지 않는다.

GPS 좌표 — rational에서 십진 도수로

EXIF 규격은 GPS 좌표를 독특한 형식으로 저장한다: 세 개의 rational 수(도, 분, 초) — 각각 분자/분모 쌍 — 그리고 반구를 나타내는 한 글자(N / S / E / W).

실제 예시:

GPSLatitudeRef:  N
GPSLatitude:     31/1, 13/1, 50/1
GPSLongitudeRef: E
GPSLongitude:    121/1, 28/1, 25/1

디코드:

위도 = 31 + 13/60 + 50/3600 = 31.230556° N
경도 = 121 + 28/60 + 25/3600 = 121.473611° E

실제 정밀도는 두 가지에 달려 있다: 카메라가 쓴 rational의 분모(분모가 1이면 초 단위 정수 해상도, 적도 부근에서 약 30 m. 휴대폰은 서브초 정밀도를 위해 훨씬 큰 분모를 자주 쓴다)와 GPS 픽스의 품질이다. 하늘이 트인 환경에서 큰 분모 rational을 쓰는 휴대폰은 몇 미터 정밀도까지 좌표를 인코딩할 수 있다. 뷰어는 단위 변환만 한다 —— 픽스의 품질을 개선하지도 악화시키지도 않는다.

뷰어는 두 축 모두 디코드해 S / W 부호 반전을 적용하고 로컬에서 Google Maps URL을 만든다. 링크는 클릭하기 전까지 요청을 보내지 않는다 —— 그 전에는 어떤 제삼자도 좌표를 보지 못한다.

SNS는 실제로 무엇을 제거하는가

“큰 플랫폼에 올렸으니 사진은 안전하겠지”라는 가정은 믿기 어렵다. 대부분의 플랫폼은 어떤 메타데이터를 남기는지 공개하지 않고, 다른 사용자에게 보여 주는 이미지와 서버에 보관하는 사본이 다를 수 있으며, 앱 버전과 업로드 경로(압축 사진, “원본”, 파일 첨부)에 따라 동작도 달라진다. 플랫폼과 상관없이 성립하는 것은 두 가지다. 첨부 파일로 보낸 것(이메일, 채팅의 “파일로 보내기”, 업로드한 파일을 그대로 제공하는 이슈 트래커)은 보통 바이트 그대로 도착한다. 그리고 플랫폼이 서버에 원본을 보관한다면 다른 사용자에게 보이지 않아도 위치 정보는 플랫폼에 남는다.

특정 경로는 직접 확인할 수 있다. GPS가 들어 있는 테스트 사진을 올리고, 다른 사용자와 같은 방식으로 내려받은 뒤, 그 파일을 이 도구로 열어 보자. 올리기 전에 로컬에서 제거하면 이 질문 자체가 사라진다.

자주 만나는 엣지 케이스

휴대폰 사진에서 “EXIF를 찾을 수 없음”

사진이 스크린샷이거나, 특정 편집 앱을 통해 내보낸 것이거나, 일부 파이프라인에서 HEIC를 JPEG로 변환하면서 메타데이터가 빠진 경우, EXIF 블록 자체가 없을 수 있다. 뷰어는 “No EXIF metadata found”라고 표시한다 —— 오히려 원하는 상태다. 편집 소프트가 추가했을 수 있는 XMP / IPTC 블록을 제거하기 위해 제거 패스는 그대로 실행해도 된다.

Photoshop “Save for Web” 후에도 EXIF가 남는 경우

메타데이터를 None으로 설정한 “Save for Web”은 EXIF를 제거하지만, 보통 ICC profile은 남기고 내보내기 과정을 기술하는 XMP 블록을 추가하기도 한다. ZeroTool 뷰어는 EXIF만 읽는다; XMP 내용을 보려면 exiftool -xmp:all 같은 XMP 지원 도구를 쓰면 된다. 제거 패스는 XMP의 APP1 세그먼트도 함께 제거한다.

HEIC / HEIF 사진

HEIC는 2017년부터 iPhone의 기본 컨테이너다. HEIC는 JPEG 마커가 아닌 ISOBMFF 구조로 EXIF를 담는다. ZeroTool 도구는 설계상 JPEG만 다룬다. HEIC의 EXIF를 제거하려면 로컬에서 exiftool -all= file.heic을 사용하거나, HEIC를 JPEG로 변환한 뒤 제거하면 된다.

TIFF, HEIC, PNG 파일

TIFF는 IFD 트리로 EXIF를 저장하며 JPEG 내부 EXIF와 구조가 비슷하지만, 파일 자체는 JPEG 마커 스트림이 아니다. PNG는 2017년 규격 확장에 따라 eXIf 청크를 가질 수 있다. ZeroTool 도구는 설계상 JPEG만 받는다 —— 다른 형식은 로컬에서 exiftool -all=을 쓰거나 JPEG로 변환한 뒤 처리하면 된다.

25 MB가 넘는 파일

도구는 파일 전체를 메모리로 읽는다. 25 MB를 넘으면 경고를 표시하고 100 MB를 넘는 파일은 거부한다. 휴대폰은 그 전에 메모리가 부족해질 수 있다. 큰 파일 여러 개를 일괄 처리할 때는 exiftool -all=나 mat2 같은 로컬 CLI가 맞다.

다른 도구로 같은 제거 재현하기

ZeroTool의 제거 처리는 30줄가량의 JavaScript 알고리즘이다. 같은 작업을 다른 세 환경에서 어떻게 적는지 살펴보자.

Bash + exiftool — 업계 표준 CLI. Phil Harvey가 Perl로 만든 도구로, Perl 자체의 라이선스(Artistic / GPL 듀얼)로 배포된다:

# 모든 메타데이터 제거 (EXIF, XMP, IPTC, ICC) — ICC를 지우면 색감이 변할 수 있음
exiftool -all= photo.jpg

# ICC 색상 프로파일은 남기고 EXIF / XMP / IPTC만 제거
exiftool -all= --icc_profile:all photo.jpg

# 남은 내용 확인 (exiftool은 원본을 photo.jpg_original로 백업한다)
exiftool photo.jpg

Python + Pillow(재인코딩):

from PIL import Image

# 재인코딩 — 약간의 화질 손실
img = Image.open("photo.jpg")
img.save("photo-clean.jpg", "JPEG", quality=95, optimize=True)

Python + piexif(재인코딩 없음):

import piexif

# 마커 스트림을 따라 걸으며 EXIF만 제거
piexif.remove("photo.jpg")

브라우저 JavaScript — ZeroTool 도구가 내부에서 하는 일:

async function stripJpegMetadata(file) {
  const buf = await file.arrayBuffer();
  const view = new DataView(buf);
  if (view.getUint16(0) !== 0xFFD8) throw new Error("not a JPEG");

  const keep = [[0, 2]];                  // SOI
  let pos = 2;
  while (pos < view.byteLength - 1) {
    const marker = view.getUint16(pos);
    if (marker === 0xFFDA || marker === 0xFFD9) {
      keep.push([pos, view.byteLength]);  // SOS부터 EOI까지
      break;
    }
    if ((marker & 0xFF00) !== 0xFF00) break;
    const segLen = view.getUint16(pos + 2);
    const segEnd = pos + 2 + segLen;
    if (marker !== 0xFFE1 && marker !== 0xFFED && marker !== 0xFFEE) {
      keep.push([pos, segEnd]);
    }
    pos = segEnd;
  }

  const total = keep.reduce((s, r) => s + r[1] - r[0], 0);
  const out = new Uint8Array(total);
  let off = 0;
  for (const [a, b] of keep) {
    out.set(new Uint8Array(buf, a, b - a), off);
    off += b - a;
  }
  return new Blob([out.buffer], { type: "image/jpeg" });
}

이 스니펫이 알고리즘 전체다. 도구의 나머지는 확인 패널을 위한 EXIF 리더이다.

ZeroTool 도구의 차이점

EXIF 도구는 많지만, ZeroTool에서는 무엇이 다른가:

기능ZeroToolexiftool(CLI)
로컬 실행, 업로드 없음예(브라우저 탭 안)예(내 컴퓨터)
재인코딩 없이 제거예(APP1 / APP13 / APP14 삭제)예(메타데이터만 다시 씀)
오픈소스예 (저장소)예
GPS 지도 링크있음없음(좌표 출력)
형식JPEG만JPEG, HEIC, TIFF, PNG, 동영상 등
배치없음(한 번에 한 파일)있음(디렉터리, -r)

“이 사진에 EXIF가 있고 제거할 수 있는가”를 한 번 확인하는 데는 설치가 필요 없는 브라우저 도구로 충분하다. 배치 처리나 HEIC 파일에는 exiftool을 쓰자.

프라이버시 메모

  • 페이지는 사진을 업로드하지 않는다. 사진 바이트는 브라우저 탭 안에 머문다. 사진에 위치 정보가 들어 있을 수 있으므로 이 도구 페이지는 Google 애널리틱스도 AdSense도 불러오지 않는다.
  • Google 지도 딥 링크는 로컬에서 만들어진다. 클릭하기 전까지 요청은 발생하지 않는다.
  • 정리된 파일 다운로드는 Blob URL이며, 메모리 잔류를 막기 위해 1초 후 revoke된다.

더 읽을거리

지금 사진의 EXIF 제거하기 →