본문 바로가기
디지털 파일 검증 연구소 디지털 파일 검증 연구소

JPEG 파일 구조 분석|SOI·APP·SOS·EOI 마커 확인 방법

디지털 파일 검증 연구소 읽는 시간 약 42분

JPEG(Joint Photographic Experts Group)는 디지털카메라 사진, 스마트폰 이미지, 웹사이트 사진 등 다양한 환경에서 사용되는 이미지 압축 기술이다.

일반적으로 .jpg 또는 .jpeg 확장자로 저장되며, 사진과 같이 색상 변화가 많은 이미지를 비교적 작은 파일 크기로 보관하는 데 적합하다.

그러나 JPEG 파일은 단순히 픽셀 데이터를 압축한 결과물만으로 구성되지 않는다.

파일의 시작을 알리는 SOI(Start of Image), 응용 프로그램 관련 정보를 저장하는 APP 마커, 이미지의 압축 설정을 정의하는 DQT·DHT·SOF, 압축 데이터의 시작을 알리는 SOS(Start of Scan), 종료를 나타내는 EOI(End of Image) 등이 일정한 규칙에 따라 배치된다.

또한 JPEG 파일에는 Exif, JFIF, ICC 색상 프로파일, XMP 등의 메타데이터가 포함될 수 있다.

이번 글에서는 JPEG 파일 구조 분석을 중심으로 주요 마커의 역할, 이미지 데이터 저장 방식, 메타데이터 식별 원리, 파일 손상 여부를 점검하는 방법을 설명한다.

Windows PowerShell을 이용해 실제 JPEG 파일의 시작 바이트와 주요 마커를 확인하는 실습도 진행한다.

1. JPEG 파일 구조 분석이 필요한 이유

JPEG 파일은 대부분의 운영체제와 이미지 프로그램에서 쉽게 열 수 있다.

하지만 파일이 열리지 않거나 이미지의 일부가 깨지는 경우에는 확장자만 확인해서 원인을 파악하기 어렵다.

다음과 같은 상황에서 JPEG 내부 구조 분석이 도움이 된다.

  • JPEG 파일이 열리지 않는 경우
  • 이미지 일부가 회색 또는 깨진 형태로 나타나는 경우
  • 파일 전송 이후 손상이 의심되는 경우
  • 확장자는 JPG지만 실제 JPEG 데이터인지 확인하려는 경우
  • 이미지의 가로·세로 크기를 헤더에서 확인하려는 경우
  • Exif 메타데이터의 존재 여부를 파악하려는 경우
  • JPEG 파일의 시작 또는 종료 데이터가 누락되었는지 확인하려는 경우
  • 원본과 수정본의 무결성을 비교하려는 경우

JPEG 구조를 이해하면 파일의 형식 오류와 이미지 데이터 손상, 메타데이터 문제를 구분하는 데 도움이 된다.

다만 JPEG 마커가 정상적으로 발견된다고 해서 이미지가 완전히 정상적으로 복원되거나 안전하다는 의미는 아니다.

2. JPEG 파일의 전체 구조

일반적인 JPEG 파일은 여러 개의 마커와 마커 세그먼트, 압축된 이미지 데이터로 구성된다.

JPEG 기본 구조

JPEG File
│
├── SOI (FF D8)
│
├── APP0 / APP1 / APP2 ...
│   ├── JFIF
│   ├── Exif / XMP
│   └── ICC Profile
│
├── DQT
│   └── Quantization Tables
│
├── SOF
│   └── Image Frame Information
│
├── DHT
│   └── Huffman Tables
│
├── DRI (Optional)
│
├── SOS
│   └── Scan Parameters
│
├── Entropy-Coded Image Data
│
└── EOI (FF D9)

위 구조는 일반적인 JPEG 파일을 이해하기 위한 개념도다.

실제 파일에서는 DQT·DHT·SOF 등의 순서와 개수가 달라질 수 있으며, Progressive JPEG는 여러 SOS와 압축 데이터 구간을 포함할 수 있다.

주요 마커의 역할

마커코드주요 역할
SOIFF D8이미지 시작
APP0FF E0JFIF 등 응용 정보
APP1FF E1Exif·XMP 등 메타데이터
APP2FF E2ICC 프로파일 등
DQTFF DB양자화 테이블 정의
DHTFF C4허프만 테이블 정의
SOF0FF C0Baseline DCT 프레임 정보
SOF2FF C2Progressive DCT 프레임 정보
DRIFF DD재시작 간격 정의
SOSFF DA스캔 시작
EOIFF D9이미지 종료

JPEG에는 위 표 이외에도 여러 마커가 존재한다.

특히 JPEG 형식은 여러 압축 프로세스를 지원하므로 모든 파일이 동일한 마커 구성을 가지는 것은 아니다.

3. JPEG 파일 시그니처와 Magic Number

JPEG 파일의 시작에는 일반적으로 다음 2바이트가 존재한다.

JPEG 시작 시그니처

FF D8

이 값은 SOI(Start of Image) 마커다.

PNG의 8바이트 시그니처와 달리 JPEG의 기본 시작 마커는 2바이트다.

대표적인 시작 바이트

JFIF 기반 JPEG 파일에서는 다음과 같은 바이트가 나타날 수 있다.

FF D8 FF E0

Exif 기반 JPEG 파일에서는 다음과 같은 바이트가 나타날 수 있다.

FF D8 FF E1

여기서 FF E0은 APP0, FF E1은 APP1 마커다.

그러나 모든 JPEG 파일이 FF D8 FF E0으로 시작하는 것은 아니다.

따라서 FF D8 뒤에 APP0이 없다는 이유만으로 JPEG 파일이 손상되었다고 판단해서는 안 된다.

4. JPEG 마커와 마커 세그먼트의 기본 구조

JPEG의 마커는 일반적으로 FF 바이트와 마커 식별 바이트로 표현된다.

하지만 모든 마커가 동일한 길이의 데이터 영역을 가지는 것은 아니다.

길이 필드가 있는 일반적인 마커 세그먼트

+----------------------+
| Marker       2 bytes |
+----------------------+
| Length       2 bytes |
+----------------------+
| Parameters  Variable |
+----------------------+

Length는 빅엔디언 방식으로 저장된다.

이 길이에는 Length 필드 자체의 2바이트가 포함되지만, 앞의 Marker 2바이트는 포함되지 않는다.

따라서 길이 필드가 있는 일반 세그먼트의 전체 크기는 다음과 같다.

전체 세그먼트 크기 = 2 + Length

길이 필드가 없는 대표적인 마커

SOI와 EOI에는 일반 세그먼트처럼 Length 필드가 존재하지 않는다.

RST0~RST7 재시작 마커와 TEM 마커도 길이 필드가 없는 마커에 해당한다.

SOS 역시 길이 필드가 있는 헤더를 가지지만, 그 뒤에는 별도의 길이 필드만으로 끝을 알 수 없는 엔트로피 부호화 데이터가 이어진다.

따라서 JPEG 파일을 분석할 때는 마커별 구조 차이를 고려해야 한다.

5. SOI 마커의 구조와 역할

SOI(Start of Image)는 JPEG 이미지 데이터 스트림의 시작을 알리는 마커다.

SOI 마커

FF D8

SOI는 JPEG 파일의 형식을 식별하는 가장 기본적인 단서다.

하지만 SOI가 존재한다고 해서 나머지 마커와 압축 데이터가 정상이라는 의미는 아니다.

파일의 일부가 잘려 있거나, 잘못된 세그먼트 길이가 기록되어 있거나, 압축 데이터가 손상된 경우에도 시작 바이트는 정상일 수 있다.

따라서 SOI 확인은 JPEG 파일 검증의 첫 단계로 이해하는 것이 적절하다.

6. APP0와 JFIF 메타데이터

APP0는 응용 프로그램 관련 정보를 저장할 수 있는 마커 세그먼트다.

일반적인 JFIF(JPEG File Interchange Format) 파일에서는 APP0에 JFIF 식별 정보가 포함된다.

JFIF 식별 바이트

4A 46 49 46 00

이는 ASCII 문자열 JFIF와 종료 바이트 00에 해당한다.

JFIF APP0의 주요 필드

필드역할
IdentifierJFIF 식별
VersionJFIF 버전
Units픽셀 밀도 단위
Xdensity수평 밀도
Ydensity수직 밀도
Xthumbnail썸네일 너비
Ythumbnail썸네일 높이
Thumbnail Data선택적인 썸네일 데이터

JFIF는 JPEG 압축 데이터의 교환을 위한 파일 형식 규칙을 제공한다.

특히 JFIF 규격에 맞는 파일에서는 SOI 다음에 JFIF APP0 세그먼트가 배치된다.

다만 JPEG 파일 전체가 반드시 JFIF 형식을 따라야 하는 것은 아니다.

7. APP1과 Exif 메타데이터

APP1은 JPEG 파일에서 Exif나 XMP 같은 메타데이터를 저장하는 데 자주 사용된다.

Exif 식별 바이트

45 78 69 66 00 00

이 값은 Exif 문자열과 두 개의 널 바이트를 나타낸다.

Exif 데이터에는 TIFF 구조를 기반으로 한 메타데이터가 포함될 수 있다.

대표적인 Exif 정보

항목의미
DateTimeOriginal원본 촬영 시각
Make장치 제조사
Model장치 모델
Orientation이미지 방향 정보
ExposureTime노출 시간
FNumber조리개 값
ISO 관련 태그촬영 감도
GPS 관련 태그위치 관련 정보

모든 JPEG 파일에 Exif가 존재하는 것은 아니다.

또한 Exif에 기록된 정보는 편집 프로그램이나 별도 도구를 통해 변경할 수 있다.

따라서 Exif 정보는 파일 분석의 단서로 활용할 수 있지만, 그 자체만으로 촬영 시각이나 촬영 장치의 진위를 확정할 수는 없다.

8. APP2와 ICC 색상 프로파일

APP2는 여러 종류의 응용 데이터를 저장할 수 있다.

그중 하나가 ICC 색상 프로파일이다.

ICC 프로파일은 이미지의 색상 표현과 색 공간 해석에 사용된다.

APP2에서 확인할 수 있는 정보

  • ICC 프로파일 식별 정보
  • 색상 프로파일 데이터
  • 여러 세그먼트로 분할된 프로파일 구성

ICC 프로파일은 용량에 따라 여러 APP2 세그먼트에 나뉘어 저장될 수 있다.

따라서 APP2가 여러 번 나타난다고 해서 곧바로 파일이 비정상적이라고 판단해서는 안 된다.

APP2 마커가 존재한다고 해서 모든 APP2가 ICC 프로파일인 것도 아니다.

9. DQT 마커와 양자화 테이블

DQT(Define Quantization Table)는 JPEG 압축 과정에서 사용하는 양자화 테이블을 정의한다.

DQT 마커

FF DB

JPEG의 일반적인 손실 압축 과정에서는 이미지 정보를 주파수 성분으로 변환한 뒤 양자화를 수행한다.

양자화는 이미지 데이터의 저장 크기를 줄이는 데 중요한 역할을 하지만, 이 과정에서 일부 정보가 손실될 수 있다.

양자화 테이블의 의미

양자화 테이블은 변환 계수에 적용할 양자화 단계를 정의한다.

이 테이블은 JPEG 압축 방식의 특성을 이해하는 중요한 정보다.

다만 양자화 테이블만 확인해서 이미지의 정확한 저장 품질이나 편집 이력을 단정할 수는 없다.

JPEG 파일에 따라 여러 개의 양자화 테이블이 정의될 수 있다.

10. DHT 마커와 허프만 테이블

DHT(Define Huffman Table)는 허프만 부호화에 사용하는 테이블을 정의한다.

DHT 마커

FF C4

허프만 부호화는 데이터에 등장하는 기호를 가변 길이 코드로 표현하는 엔트로피 부호화 방식이다.

JPEG에서는 허프만 테이블을 사용해 압축된 데이터의 비트열을 해석할 수 있다.

그러나 JPEG 표준에는 허프만 부호화 외의 엔트로피 부호화 방식도 존재한다.

따라서 모든 JPEG 파일이 반드시 DHT 마커를 포함한다고 일반화해서는 안 된다.

11. SOF 마커와 이미지 크기 정보

SOF(Start of Frame)는 JPEG 이미지의 프레임 특성을 정의한다.

대표적으로 SOF0과 SOF2가 자주 등장한다.

주요 SOF 마커

마커의미
SOF0 (FF C0)Baseline DCT
SOF1 (FF C1)Extended Sequential DCT
SOF2 (FF C2)Progressive DCT

SOF 세그먼트에는 이미지 크기와 구성 요소 정보 등이 기록된다.

주요 필드

필드의미
Sample Precision샘플 정밀도
Image Height이미지 높이
Image Width이미지 너비
Number of Components색상 구성 요소 수
Component Parameters구성 요소별 샘플링·테이블 정보

SOF에 기록된 이미지 크기를 읽으면 전체 이미지를 디코딩하지 않고도 가로와 세로 픽셀 수를 확인할 수 있다.

다만 모든 JPEG 계열의 압축 프로세스가 SOF0 또는 SOF2만 사용하는 것은 아니다.

12. SOS 마커와 압축 이미지 데이터

SOS(Start of Scan)는 스캔의 시작을 나타내는 마커다.

SOS 마커

FF DA

SOS 세그먼트에는 스캔에 포함되는 구성 요소와 엔트로피 부호화에 필요한 일부 매개변수가 저장된다.

SOS 헤더 뒤에는 엔트로피 부호화된 이미지 데이터가 이어진다.

SOS 이후의 특징

JPEG의 압축 이미지 데이터는 일반적인 길이 필드가 있는 마커 세그먼트와 다르게 처리해야 한다.

압축 데이터에는 FF 바이트가 포함될 수 있으며, 실제 데이터의 FF 값과 마커를 구분하기 위해 바이트 스터핑(Byte Stuffing)이 사용된다.

대표적인 형태는 다음과 같다.

FF 00

이는 엔트로피 부호화 데이터 안에서 FF 데이터 바이트를 표현하기 위한 방식이다.

따라서 JPEG 파일에서 FF 바이트를 무조건 새로운 마커의 시작으로 해석해서는 안 된다.

Progressive JPEG의 특징

Progressive JPEG는 여러 스캔으로 이미지 정보를 나누어 저장할 수 있다.

이 경우 SOS가 여러 번 등장할 수 있으며, 각 스캔 사이에 추가 마커가 존재할 수도 있다.

따라서 첫 번째 SOS 뒤의 모든 데이터를 하나의 고정 길이 영역으로 간주하는 방식은 올바르지 않다.

13. EOI 마커와 JPEG 종료 구조

EOI(End of Image)는 JPEG 이미지 데이터 스트림의 종료를 나타낸다.

EOI 마커

FF D9

일반적인 단일 JPEG 파일에서는 압축 이미지 데이터가 끝난 뒤 EOI가 등장한다.

EOI 확인이 필요한 이유

  • 파일이 중간에 잘렸는지 점검하는 단서가 된다.
  • 압축 데이터의 종료 구조를 확인하는 데 도움이 된다.
  • 파일 전송 과정에서 일부 데이터가 누락되었는지 조사할 때 활용할 수 있다.

그러나 파일 끝부분에 FF D9가 존재한다고 해서 압축 데이터가 정상이라는 의미는 아니다.

또한 EOI 뒤에 추가 데이터가 존재할 수도 있으므로 파일의 끝 2바이트만 확인하는 방식으로 모든 JPEG 파일의 구조적 정상 여부를 판단할 수는 없다.

14. JPEG와 PNG의 내부 구조 비교

JPEG와 PNG는 모두 널리 사용되는 이미지 형식이지만 내부 구조와 검증 방식이 다르다.

구분JPEGPNG
시작 식별값FF D889 50 4E 47 0D 0A 1A 0A
기본 구조마커·세그먼트·압축 데이터청크
일반적인 압축손실 압축무손실 압축
이미지 크기 정보SOF 등IHDR
메타데이터APP 세그먼트 등메타데이터 청크
종료 식별EOIIEND
구조적 CRC 필드일반 JPEG에 없음각 청크에 존재

PNG는 각 청크에 CRC-32 필드가 포함되지만, 일반적인 JPEG 마커 세그먼트에는 이에 대응하는 공통 CRC 필드가 존재하지 않는다.

따라서 JPEG 파일의 무결성을 확인하려면 마커 구조 점검 외에도 파일 전체의 해시값 비교 등을 고려할 수 있다.

15. Windows PowerShell로 JPEG 시그니처 확인하기

이제 실제 JPEG 파일의 시작 바이트를 확인해 보자.

이번 실습은 파일을 수정하지 않는 읽기 전용 방식이다.

① 실습 파일 준비

다음 경로에 JPEG 파일이 있다고 가정한다.

C:\FileTest\sample.jpg

실제 실행할 때는 본인의 파일 경로로 변경한다.

② PowerShell 코드

$path = "C:\FileTest\sample.jpg"

$stream = [System.IO.File]::OpenRead($path)

try {
    $buffer = New-Object byte[] 4
    $count = $stream.Read($buffer, 0, 4)

    if ($count -lt 2) {
        throw "File is shorter than 2 bytes."
    }

    if ($buffer[0] -ne 0xFF -or
        $buffer[1] -ne 0xD8) {
        throw "Invalid JPEG SOI marker."
    }

    [BitConverter]::ToString($buffer, 0, $count)
}
finally {
    $stream.Dispose()
}

③ 출력 결과 예시

FF-D8-FF-E0

이 결과는 JFIF 기반 JPEG 파일에서 나타날 수 있는 시작 바이트의 예시다.

Exif 기반 JPEG 파일이라면 FF-D8-FF-E1과 같은 결과가 나타날 수 있다.

④ 확인 가능한 범위

이 코드는 JPEG의 시작 SOI 마커를 확인하고 최대 4바이트를 출력한다.

SOF, SOS, EOI, 압축 데이터의 정상 여부는 확인하지 않는다.

16. PowerShell로 JPEG 주요 마커 목록 확인하기

JPEG 파일의 주요 마커를 읽으면 파일의 내부 구성을 이해하는 데 도움이 된다.

이번 실습은 SOI에서 시작해 길이 필드가 있는 세그먼트를 읽고, SOS 이후의 압축 데이터에서 바이트 스터핑과 재시작 마커를 구분하도록 구성했다.

① PowerShell 코드

$path = "C:\FileTest\sample.jpg"

$stream = [System.IO.File]::OpenRead($path)

function Read-ByteRequired {
    param($InputStream)

    $value = $InputStream.ReadByte()

    if ($value -lt 0) {
        throw "Unexpected end of file."
    }

    return $value
}

try {
    $first = Read-ByteRequired $stream
    $second = Read-ByteRequired $stream

    if ($first -ne 0xFF -or $second -ne 0xD8) {
        throw "Invalid JPEG SOI marker."
    }

    [PSCustomObject]@{
        Offset = 0
        Marker = "FFD8"
        Name   = "SOI"
        Length = "-"
    }

    $insideScan = $false
    $pendingMarker = -1
    $pendingOffset = [long]0

    while ($stream.Position -lt $stream.Length -or
           $pendingMarker -ge 0) {

        if ($pendingMarker -ge 0) {
            $marker = $pendingMarker
            $offset = $pendingOffset
            $pendingMarker = -1
        }
        else {
            $prefix = Read-ByteRequired $stream

            if ($prefix -ne 0xFF) {
                if ($insideScan) {
                    continue
                }

                throw "Expected marker prefix FF."
            }

            $offset = $stream.Position - 1

            do {
                $marker = Read-ByteRequired $stream
            } while ($marker -eq 0xFF)
        }

        if ($marker -eq 0x00) {
            if (-not $insideScan) {
                throw "Unexpected stuffed byte."
            }

            continue
        }

        $code = "FF{0:X2}" -f $marker
        $name = "Other"

        if ($marker -eq 0xD9) {
            $name = "EOI"
        }
        elseif ($marker -eq 0xDA) {
            $name = "SOS"
        }
        elseif ($marker -ge 0xE0 -and $marker -le 0xEF) {
            $name = "APP{0}" -f ($marker - 0xE0)
        }
        elseif ($marker -eq 0xDB) {
            $name = "DQT"
        }
        elseif ($marker -eq 0xC4) {
            $name = "DHT"
        }
        elseif ($marker -eq 0xDD) {
            $name = "DRI"
        }
        elseif ($marker -eq 0xC0) {
            $name = "SOF0"
        }
        elseif ($marker -eq 0xC2) {
            $name = "SOF2"
        }
        elseif ($marker -ge 0xD0 -and $marker -le 0xD7) {
            $name = "RST{0}" -f ($marker - 0xD0)
        }
        elseif ($marker -eq 0xFE) {
            $name = "COM"
        }

        if ($marker -eq 0xD9) {
            [PSCustomObject]@{
                Offset = $offset
                Marker = $code
                Name   = $name
                Length = "-"
            }

            break
        }

        if (($marker -ge 0xD0 -and $marker -le 0xD7) -or
            $marker -eq 0x01) {

            [PSCustomObject]@{
                Offset = $offset
                Marker = $code
                Name   = $name
                Length = "-"
            }

            continue
        }

        $high = Read-ByteRequired $stream
        $low = Read-ByteRequired $stream

        $length = ($high -shl 8) -bor $low

        if ($length -lt 2) {
            throw "Invalid marker segment length."
        }

        $payloadLength = $length - 2
        $remaining = $stream.Length - $stream.Position

        if ($payloadLength -gt $remaining) {
            throw "Segment exceeds file boundary."
        }

        [PSCustomObject]@{
            Offset = $offset
            Marker = $code
            Name   = $name
            Length = $length
        }

        $stream.Seek(
            [long]$payloadLength,
            [System.IO.SeekOrigin]::Current
        ) | Out-Null

        if ($marker -eq 0xDA) {
            $insideScan = $true
        }
        else {
            $insideScan = $false
        }

        if ($insideScan) {
            while ($stream.Position -lt $stream.Length) {
                $value = Read-ByteRequired $stream

                if ($value -ne 0xFF) {
                    continue
                }

                $candidateOffset = $stream.Position - 1

                do {
                    $next = Read-ByteRequired $stream
                } while ($next -eq 0xFF)

                if ($next -eq 0x00) {
                    continue
                }

                if ($next -ge 0xD0 -and $next -le 0xD7) {
                    [PSCustomObject]@{
                        Offset = $candidateOffset
                        Marker = ("FF{0:X2}" -f $next)
                        Name   = ("RST{0}" -f ($next - 0xD0))
                        Length = "-"
                    }

                    continue
                }

                $pendingMarker = $next
                $pendingOffset = $candidateOffset
                break
            }

            if ($pendingMarker -lt 0) {
                throw "JPEG scan ended without a marker."
            }
        }
    }
}
finally {
    $stream.Dispose()
}

② 출력 결과 예시

Offset Marker Name Length
------ ------ ---- ------
     0 FFD8   SOI  -
     2 FFE0   APP0 16
    20 FFDB   DQT  67
    89 FFC0   SOF0 17
   108 FFC4   DHT  31
   141 FFDA   SOS  12
  8462 FFD9   EOI  -

위 결과는 형식을 설명하기 위한 예시이며 실제 파일의 마커 순서와 오프셋, 세그먼트 길이는 다를 수 있다.

③ 이 코드로 확인할 수 있는 것

  • SOI 시작 마커
  • APP0·APP1 등의 응용 세그먼트
  • DQT·DHT·SOF0·SOF2·SOS 마커
  • 길이 필드가 있는 세그먼트의 크기
  • 일반적인 스캔 데이터의 바이트 스터핑
  • RST0~RST7 재시작 마커
  • EOI 종료 마커의 등장 여부

④ 실습 코드의 한계

이 코드는 JPEG 구조를 살펴보기 위한 학습용 마커 탐색기다.

모든 JPEG 압축 프로세스와 확장 형식의 문법을 검증하는 완전한 디코더는 아니다.

또한 이미지 픽셀을 복원하거나 Exif 내용을 해석하지 않으며, 허프만 데이터와 양자화 테이블의 의미적 정합성을 검사하지 않는다.

따라서 출력된 마커 목록만으로 파일의 정상적인 디코딩 가능 여부를 확정해서는 안 된다.

17. PowerShell로 JPEG 이미지 크기 확인하기

이번에는 JPEG의 SOF0 또는 SOF2 세그먼트에서 이미지의 가로·세로 크기를 읽어 보자.

① PowerShell 코드

$path = "C:\FileTest\sample.jpg"

$bytes = [System.IO.File]::ReadAllBytes($path)

if ($bytes.Length -lt 4 -or
    $bytes[0] -ne 0xFF -or
    $bytes[1] -ne 0xD8) {
    throw "Invalid JPEG SOI marker."
}

$position = 2
$found = $false

while ($position + 3 -lt $bytes.Length) {

    if ($bytes[$position] -ne 0xFF) {
        throw "Invalid marker boundary."
    }

    while ($position -lt $bytes.Length -and
           $bytes[$position] -eq 0xFF) {
        $position++
    }

    if ($position -ge $bytes.Length) {
        break
    }

    $marker = $bytes[$position]
    $position++

    if ($marker -eq 0xD9 -or $marker -eq 0xDA) {
        break
    }

    if ($marker -eq 0x01 -or
        ($marker -ge 0xD0 -and $marker -le 0xD7)) {
        continue
    }

    if ($position + 1 -ge $bytes.Length) {
        throw "Incomplete segment length."
    }

    $length = (
        ($bytes[$position] -shl 8) -bor
        $bytes[$position + 1]
    )

    if ($length -lt 2 -or
        $length -gt $bytes.Length - $position) {
        throw "Invalid segment length."
    }

    if ($marker -eq 0xC0 -or $marker -eq 0xC2) {

        if ($length -lt 8) {
            throw "SOF segment is too short."
        }

        $height = (
            ($bytes[$position + 3] -shl 8) -bor
            $bytes[$position + 4]
        )

        $width = (
            ($bytes[$position + 5] -shl 8) -bor
            $bytes[$position + 6]
        )

        [PSCustomObject]@{
            FrameType = if ($marker -eq 0xC0) {
                "Baseline"
            } else {
                "Progressive"
            }
            Width = $width
            Height = $height
            Precision = $bytes[$position + 2]
        }

        $found = $true
        break
    }

    $position += $length
}

if (-not $found) {
    Write-Output "SOF0 or SOF2 was not found."
}

② 출력 예시

FrameType : Baseline
Width     : 1920
Height    : 1080
Precision : 8

위 결과는 예시다.

③ 결과 해석

FrameType은 SOF0 또는 SOF2에 따라 표시된다.

Width와 Height는 이미지의 픽셀 크기다.

Precision은 이미지 샘플의 정밀도를 나타낸다.

④ 주의사항

이 코드는 SOF0과 SOF2만 찾는다.

다른 SOF 마커를 사용하는 JPEG 파일에서는 크기를 찾지 못할 수 있다.

또한 파일 전체를 메모리에 읽으므로 대용량 파일이나 출처가 불분명한 파일에 사용할 때는 별도의 크기 제한이 필요하다.

18. JPEG 파일 손상 시 점검할 주요 항목

JPEG 파일에 문제가 발생하면 다음 항목을 순서대로 확인할 수 있다.

점검 항목확인 목적
SOIJPEG 시작 여부
APP0·APP1JFIF·Exif 메타데이터 존재 여부
DQT양자화 테이블 존재 여부
DHT허프만 테이블 존재 여부
SOF이미지 크기와 프레임 정보
SOS스캔 시작 구조
RST재시작 마커 사용 여부
EOI이미지 종료 여부
SHA-256기준 파일과의 바이트 동일성 비교

손상이 의심되는 대표적인 상황

① SOI 누락

파일이 JPEG 데이터 스트림으로 시작하지 않거나 앞부분이 손상되었을 가능성이 있다.

② 세그먼트 길이 오류

세그먼트에 기록된 길이가 파일 경계를 벗어나면 데이터가 잘렸거나 구조가 잘못되었을 수 있다.

③ SOS 이후 압축 데이터 손상

이미지가 일부만 표시되거나 색상이 깨질 수 있다.

④ EOI 누락

파일이 정상적으로 종료되지 않았거나 전송 과정에서 일부 데이터가 누락되었을 가능성이 있다.

⑤ 메타데이터 이상

일부 프로그램에서 이미지 방향이나 색상 표현이 예상과 다르게 표시될 수 있다.

다만 이러한 증상만으로 손상의 원인을 확정해서는 안 된다.

19. JPEG 구조 검증과 SHA-256 무결성 검증

JPEG 파일의 구조를 확인하는 것과 원본 파일의 동일성을 검증하는 것은 서로 다른 작업이다.

JPEG 구조 검증

마커와 세그먼트의 배치, 길이, 이미지 프레임 정보 등을 확인한다.

메타데이터 확인

APP 세그먼트에 포함된 Exif, JFIF, ICC 프로파일 등의 존재와 일부 내용을 확인한다.

SHA-256 검증

파일 전체의 바이트를 대상으로 SHA-256 해시값을 계산하고 신뢰할 수 있는 기준값과 비교한다.

검증 방식 비교

구분JPEG 구조 분석메타데이터 분석SHA-256
확인 대상마커와 압축 구조APP 데이터 등파일 전체
주요 목적형식 점검부가 정보 확인바이트 동일성 비교
원본 동일성 증명불가능불가능신뢰할 수 있는 기준값이 있으면 가능
안전성 보장불가능불가능단독으로는 불가능

JPEG 파일이 정상적으로 열리더라도 원본과 동일하다는 보장은 없다.

또한 이미지를 다시 저장하면 압축 데이터나 메타데이터가 변경되어 SHA-256 해시값이 달라질 수 있다.

20. JPEG 파일 분석 시 보안과 개인정보 주의사항

① Exif 위치 정보

일부 JPEG 파일에는 GPS 관련 메타데이터가 포함될 수 있다.

사진을 공개하기 전에 불필요한 위치 정보가 포함되어 있는지 확인하는 것이 좋다.

② 촬영 정보의 신뢰성

Exif에 기록된 촬영 날짜나 장치 모델은 변경될 수 있다.

따라서 메타데이터만으로 촬영 시점이나 장치의 진위를 확정해서는 안 된다.

③ 비정상적인 파일 구조

출처가 불분명한 JPEG 파일에는 잘못된 길이 값이나 비정상적인 데이터가 포함될 수 있다.

신뢰할 수 없는 파일을 분석할 때는 최신 보안 업데이트가 적용된 프로그램을 사용하고, 파일 크기와 리소스 사용량을 제한하는 것이 바람직하다.

④ 원본 파일 보존

무결성 검증이나 손상 분석이 목적이라면 원본을 보존하고 복사본으로 작업하는 것이 좋다.

편집 프로그램에서 다시 저장하면 JPEG 압축 데이터가 변경될 수 있다.

21. 자주 묻는 질문

Q1. JPEG 파일의 Magic Number는 무엇인가요?

일반적인 JPEG 데이터 스트림은 SOI 마커인 FF D8로 시작한다.

Q2. 모든 JPEG 파일이 FF D8 FF E0으로 시작하나요?

아니다. JFIF 기반 파일에서는 APP0가 등장할 수 있지만, Exif 기반 파일에서는 APP1이 등장하는 등 구성이 다를 수 있다.

Q3. APP1에는 항상 Exif가 들어 있나요?

아니다. APP1은 Exif뿐 아니라 XMP 등의 데이터를 저장하는 데에도 사용될 수 있다.

Q4. JPEG 파일의 이미지 크기는 어디에 기록되나요?

일반적인 JPEG에서는 SOF 세그먼트에 이미지의 가로·세로 크기와 프레임 정보가 기록된다.

Q5. SOS 마커는 항상 한 번만 등장하나요?

아니다. Progressive JPEG에서는 여러 스캔을 사용하므로 SOS가 여러 번 등장할 수 있다.

Q6. JPEG에는 PNG처럼 CRC-32가 있나요?

일반적인 JPEG 마커 세그먼트에는 PNG 청크처럼 공통 CRC-32 필드가 존재하지 않는다.

Q7. EOI 마커가 있으면 파일이 정상인가요?

반드시 그렇지는 않다. EOI가 있어도 압축 데이터나 다른 세그먼트가 손상되어 있을 수 있다.

Q8. JPEG와 JPG는 서로 다른 파일 형식인가요?

일반적으로 .jpg와 .jpeg는 같은 JPEG 이미지 형식을 나타내는 확장자로 사용된다.

마무리

JPEG 파일은 SOI, APP, DQT, DHT, SOF, SOS, EOI 등의 마커와 압축 이미지 데이터로 구성된다.

SOI는 이미지 데이터 스트림의 시작을 알리고, APP 세그먼트는 JFIF·Exif 등의 부가 정보를 저장하며, SOF는 이미지 크기와 프레임 정보를 정의한다.

SOS 이후에는 엔트로피 부호화된 이미지 데이터가 이어지고, EOI는 데이터 스트림의 종료를 나타낸다.

그러나 JPEG 파일 구조 분석은 형식과 내부 구성을 확인하는 작업이지, 이미지의 출처나 원본 동일성 또는 보안상 안전성을 보장하는 과정은 아니다.

따라서 마커 구조 확인, 메타데이터 분석, SHA-256 해시 검증은 각각의 목적에 맞게 구분하여 활용해야 한다.


핵심 내용 3줄 요약

  • JPEG는 FF D8 SOI 마커로 시작하며 APP·SOF·SOS 등의 마커를 통해 이미지 구조를 정의한다.
  • APP 세그먼트에는 JFIF·Exif·ICC 프로파일 등 다양한 메타데이터가 포함될 수 있다.
  • JPEG 마커 구조가 정상이어도 압축 데이터의 정상성이나 원본 동일성이 보장되지는 않는다.

공식 및 기술 참고자료

  1. ITU-T — Recommendation T.81: Digital Compression and Coding of Continuous-Tone Still Images
    https://www.itu.int/rec/T-REC-T.81
  2. W3C — JPEG File Interchange Format (JFIF), Version 1.02
    https://www.w3.org/Graphics/JPEG/jfif.pdf
  3. Library of Congress — JPEG Image Encoding Family
    https://www.loc.gov/preservation/digital/formats/fdd/fdd000017.shtml
  4. Library of Congress — JFIF, JPEG File Interchange Format, Version 1.02
    https://www.loc.gov/preservation/digital/formats/fdd/fdd000018.shtml
  5. ITU-T — Recommendation T.86: APPn Markers
    https://www.itu.int/rec/T-REC-T.86
  6. Microsoft Learn — File.OpenRead Method
    https://learn.microsoft.com/dotnet/api/system.io.file.openread
  7. Microsoft Learn — File.ReadAllBytes Method
    https://learn.microsoft.com/dotnet/api/system.io.file.readallbytes

※ 본문의 PowerShell 코드는 JPEG의 주요 마커와 일부 프레임 정보를 읽기 전용으로 확인하는 학습용 예시다. 완전한 JPEG 디코더 또는 모든 JPEG 확장 형식의 적합성 검증 도구를 대체하지 않는다.

디지털 파일 검증 연구소

디지털 파일 검증 연구소
디지털 파일 검증 연구소는 파일 형식, 확장자, 무결성 확인, 데이터 손상 예방 및 디지털 자료 관리 방법을 다루는 정보 사이트입니다. 파일 오류의 원인과 점검 절차를 이해하기 쉽게 정리하고, 공식 문서와 검증 가능한 자료를 바탕으로 실생활에 도움이 되는 해결 방법을 안내합니다.
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.