증분 업데이트와 그것이 중요한 이유
ISO 32000-2 §7.5.6 Evidence: Standard-backed
한눈에 보기
섹션 제목: “한눈에 보기”PDF가 작성된 뒤에 변경될 때, 안전하게 저장하는 방법은 파일을 다시 쓰는 것이 아닙니다. 대신, 변경된 객체와 새로운 크로스 레퍼런스 섹션을 끝에 추가하고, 모든 원본 바이트를 정확히 원래 자리에 그대로 둡니다. 이 페이지는 그 동작 방식과, 그것이 디지털 서명이 이후의 편집을 견뎌 낼 수 있는 이유인 까닭을 설명합니다.
왜 중요한가
섹션 제목: “왜 중요한가”서명은 일정 범위의 바이트를 보호합니다. 한 단어짜리 변경을 저장하는 일이 파일을 다시 쓰는 것이라면, 모든 바이트 오프셋이 이동하게 됩니다. 서명된 범위는 더 이상 동일한 내용을 가리키지 않습니다. 서명된 내용 자체는 손대지 않았더라도 서명은 깨집니다.
증분 업데이트는 바로 그런 일이 일어나지 않도록 존재합니다. 서명이 포함하는 바이트를 비롯한 원본 바이트는 제자리에 그대로 있습니다. 검토자는 서명 후 편집된 문서를 받아 첫 번째 서명을 원래 개정에 대조하여 검증할 수 있습니다. 검토자는 무엇이 서명되었는지를 정확히 확인하고, 그와 별개로 이후에 무엇이 변경되었는지를 확인합니다. 이를 잘못 다루면, 정당한 서명을 무효화하거나, 더 나쁘게는 서명이 실제로 무엇을 증명했는지를 입증할 능력을 잃게 됩니다.
- 증분 업데이트는 추가합니다. 새로운 객체와 변경된 객체, 그다음 새로운 크로스 레퍼런스 섹션, 그다음 새로운 트레일러를 모두 파일의 끝에 추가합니다.
- 원본 파일 내용은 제자리에서 편집되지 않고 그대로 남습니다.
- 새로운 트레일러는
/Prev엔트리를 가집니다. 이는 이전 크로스 레퍼런스 섹션의 바이트 오프셋입니다. 섹션들은 뒤로 향하는 사슬을 이룹니다. - 리더는 그 사슬을 최신 순서로 따라가며 인덱스를 구성합니다. 어떤 객체 번호에 대해서든 가장 최근의 엔트리가 우선합니다.
- 아무것도 덮어쓰지 않았으므로, 이전 서명이 포함하던 바이트 범위는 여전히 바이트 단위로 동일합니다. 따라서 서명은 여전히 검증되며, 서명되었던 그대로 문서를 정확히 복원할 수 있습니다.
NextPDF가 접근하는 방식
섹션 제목: “NextPDF가 접근하는 방식”NextPDF는 이전 페이지에서 설명한 대로 기본 문서를 작성한 다음, 증분 업데이트에 필요한 세 가지를 노출합니다.
build() 이후, 라이터(src/Writer/PdfWriter.php)는 다음을 유지합니다.
- 출력 버퍼.
getBuffer()로 가져올 수 있으며, 업데이트를 기존 바이트의 정확한 끝에 추가할 수 있습니다. - 마지막 크로스 레퍼런스 섹션의 바이트 오프셋.
getLastXrefOffset()로 가져올 수 있으며, 이것이 새 섹션의/Prev값이 됩니다. - 카탈로그 딕셔너리 엔트리.
getCatalogEntries()로 가져올 수 있으며, 카탈로그를 다시 내보내야 하는 업데이트(예: 서명 참조를 부착하는 경우)에서도 이전 키를 잃지 않습니다.
추가된 개정은 동일한 ObjectRegistry에 대해 새 객체 번호를 할당합니다(또는 교체하는 객체에 대해서는 기존 번호를 재사용합니다). 따라서 객체 번호 부여는 개정 전반에 걸쳐 일관성을 유지합니다. 새 크로스 레퍼런스 섹션은 이 개정이 손댄 객체만을 나열합니다. 새 트레일러는 이전 트레일러의 엔트리를 반복하고, 이전 섹션을 가리키는 /Prev를 추가합니다. 리더가 따라가는 것이 바로 그 사슬입니다.
이것이 가장 명확하게 드러나는 곳은 서명입니다. NextPDF의 ByteRangeCalculator(src/Security/Signature/ByteRangeCalculator.php)는 /ByteRange 배열을 두 개의 세그먼트로 계산합니다. 서명 값 앞의 모든 것과 그 뒤의 모든 것입니다. 따라서 서명은 자신의 바이트를 제외한 개정 전체를 포함합니다. 이후의 편집은 그 바이트 위에 덮어쓰는 대신 추가되므로, 그 범위는 결코 이동하지 않습니다.
- Write base revision Header, body, xref section, trailer — the original bytes.
- Sign A /ByteRange digest covers the whole revision except the signature value itself.
- Edit and save Changed objects + a new xref section are appended; originals are untouched.
- New trailer chains back The appended trailer carries /Prev = offset of the previous xref section.
- Verify The first signature still covers the same unchanged bytes; the chain shows what came after.
증거가 말하는 것
섹션 제목: “증거가 말하는 것”추가 전용 규칙은 규범적입니다. Spec: ISO 32000-2, §7.5.6 ISO 32000-2 §7.5.6 는 PDF의 내용이 파일 전체를 다시 쓰지 않고도 증분적으로 업데이트될 수 있으며, 그렇게 할 때 변경은 파일의 끝에 추가되어야 하고(shall) 원본 내용은 손대지 않은 채 남는다고 명시합니다. Evidence: Standard-backed
같은 조항이 그 동작 방식을 정의합니다. 증분 업데이트의 크로스 레퍼런스 섹션에는 변경되거나 교체되거나 삭제된 객체에 대한 엔트리만 들어갑니다. 삭제된 객체는 파일 안에 남지만, 자신의 크로스 레퍼런스 엔트리를 통해 삭제됨으로 표시됩니다. 추가되는 트레일러에는 이전 크로스 레퍼런스 섹션의 위치를 알려 주는 /Prev 엔트리가 들어 있어야 합니다(shall). 변경된 객체에 대한 업데이트의 엔트리는 새 사본의 바이트 오프셋을 가지며, 이전 오프셋을 무효화합니다. 리더는 각 객체의 가장 최근 사본에 접근하도록 자신의 크로스 레퍼런스 정보를 구성합니다.
서명상의 결과는 Spec: ISO 32000-2, §12.8.1 ISO 32000-2 §12.8.1 에 직접 명시되어 있습니다. 바이트 범위 다이제스트는 파일의 일정 범위에 걸쳐 계산되며, 보통은 파일 전체에 걸치되 서명 값(/Contents 엔트리)은 제외합니다. 이어서 표준은, 서명된 문서가 증분 업데이트로 수정되어 저장되면 원래 서명의 바이트 범위에 해당하는 데이터가 보존되므로, 서명이 유효하다면 서명 시점의 문서 상태를 복원할 수 있다고 언급합니다. 추가 전용은 있으면 좋은 정도가 아닙니다. 그것은 서명 모델이 의존하는 속성입니다.
실용적인 예제
섹션 제목: “실용적인 예제”서명 후 편집된 PDF를 구조적으로 본 것입니다. 원래 개정은 자신의 %%EOF로 끝납니다. 두 번째 개정은 그 아래에 추가됩니다.
%PDF-2.0... original objects, including the signature dictionary ...xref0 8... entries for the original revision ...trailer<< /Size 8 /Root 1 0 R >>startxref920%%EOF <-- end of revision 1: the signed bytes stop here9 0 obj <-- revision 2, appended<< /Type /Annot /Subtype /Text /Contents (added after signing) >>endobjxref0 19 0 obj-entry...8 90000001740 00000 ntrailer<< /Size 10 /Root 1 0 R /Prev 920 >>startxref1980%%EOF검증기는 마지막 트레일러를 읽고 /Prev 920을 확인하여 이제 사슬 전체를 갖게 됩니다. 변경되지 않은 첫 번째 %%EOF까지의 바이트에 대해 서명을 검증할 수 있습니다. 그런 다음 개정 2가 주석을 추가했다는 사실을 별도로 보고할 수 있습니다. 이력은 파일 안에 있습니다. 덮어쓰기로 숨겨진 것은 아무것도 없습니다.
흔한 오해
섹션 제목: “흔한 오해”함정은 “증분 업데이트는 변경이 작다는 뜻이므로 무해하다”는 생각입니다. 추가는 바이트 보존에 관한 것이지 크기에 관한 것이 아닙니다. 증분 업데이트는 상당히 많은 내용을 추가할 수 있습니다. 그것을 증분 업데이트로 만드는 것은, 이미 그곳에 있던 바이트에 손대지 않는다는 점입니다. 그 따름정리도 사람들을 곤란하게 합니다. 서명된 PDF를 처음부터 다시 써서 “최적화”하거나 “선형화”하는 도구는 더 작고 깔끔한 파일을 그리고 깨진 서명을 만들어 냅니다. 서명된 바이트 범위가 더 이상 존재하지 않기 때문입니다. 서명된 PDF를 저장하는 것과 다시 저장하는 것은 같은 작업이 아닙니다.
한계와 경계
섹션 제목: “한계와 경계”추가 전용은 바이트를 보호합니다. 그것 자체로는 추가된 변경이 승인된 것인지를 알려 주지 않습니다. 두 번째 개정은 정당하게 두 번째 서명을 추가할 수도 있고, 첫 번째 서명자가 결코 의도하지 않은 내용을 추가할 수도 있습니다. 어느 쪽인지를 판단하는 것은 서명 검증과 변경 탐지 정책(DocMDP)의 몫입니다. 추가는 그 분석을 가능하게 하는 토대이지, 분석 그 자체는 아닙니다.
이 페이지는 또한 서명의 두 바이트 범위가 어떻게 계산되고 이어 붙여지는지, 또 완전한 검증이 무엇을 확인하는지도 다루지 않습니다. 그것들은 별도의 주제입니다. 그리고 여기서의 보장은 적합한 라이터에 의해 작성되고 업데이트된 파일에 관한 것입니다. 이전 개정이 이미 잘못된 형식이었던 파일은 거기에 무언가를 추가한다고 해서 올바른 형식이 되지 않습니다.
미니 FAQ
섹션 제목: “미니 FAQ”PDF에 개정이 몇 개 있는지 어떻게 알 수 있습니까?
%%EOF 마커를 세고, 마지막 트레일러에서 /Prev 사슬을 따라가십시오. 도달하는 각 크로스 레퍼런스 섹션이 저장된 하나의 개정입니다.
객체를 삭제하면 파일에서 제거됩니까? 아니요. 증분 업데이트는 객체를 그 크로스 레퍼런스 엔트리에서 삭제됨으로 표시하지만, 객체의 바이트는 이전 개정에 그대로 남습니다. “삭제됨”은 “지워짐”이 아니라 “현재 개정에서 참조되지 않음”을 뜻합니다.
증분 업데이트로 PDF 버전을 변경할 수 있습니까?
예. 추가된 개정의 카탈로그에서 /Version 엔트리를 설정하면 됩니다. 헤더는 작성된 그대로 남습니다. 카탈로그의 /Version은 더 최신 버전을 지정할 때 우선합니다.
관련 문서
섹션 제목: “관련 문서”- PDF란 실제로 무엇인가 — 객체 모델과 업데이트가 확장하는 단일 크로스 레퍼런스 섹션에 대해.
- 서명은 PDF 안 어디에 자리 잡는가 — 증분 업데이트가 보호하기 위해 존재하는 바이트 범위 메커니즘에 대해.
- 서명을 올바르게 검증하기 — 올바른 검증이 파일의 개정 이력 전반에 걸쳐 무엇을 확인하는지에 대해.
용어집
섹션 제목: “용어집”- 증분 업데이트 — 기존 바이트를 변경하지 않고, 변경된 객체와 새로운 크로스 레퍼런스 섹션, 그리고 새로운 트레일러를 파일의 끝에 추가하여 변경을 저장하는 것.
/Prev— 이전 크로스 레퍼런스 섹션의 바이트 오프셋을 가진 트레일러(또는 크로스 레퍼런스 스트림) 엔트리. 개정들을 뒤로 향하는 사슬로 연결합니다.- 개정(revision) — 하나의 크로스 레퍼런스 섹션과 그 트레일러가 포착하는 파일 상태. N개의 크로스 레퍼런스 섹션을 가진 파일에는 N개의 개정이 있습니다.
/ByteRange— 서명 다이제스트가 포함하는 두 바이트 세그먼트(서명 값 자체를 제외한 모든 것)를 알려 주는 서명 딕셔너리의 배열.- 서명된 바이트 범위 — 서명의 다이제스트가 계산된 정확한 바이트. 증분 업데이트는 이 바이트가 결코 이동되거나 덮어쓰이지 않도록 존재합니다.