Technical Article

การปกปิดข้อมูล PDF ที่แท้จริงใน Delphi: ลบและทำความสะอาด

การปกปิดข้อมูล PDF ที่แท้จริงใน Delphi หมายถึงการลบไบต์ที่เป็นข้อมูลเบื้องหลังออกไป ไม่ใช่แค่การทาสีทับลงไป losLab PDF Library แยกความแตกต่างนี้ด้วยสอง API: RedactRegion จะแก้ไขสตรีมเนื้อหาของหน้ากระดาษเพื่อให้ข้อความ เวกเตอร์ และรูปภาพที่ลบออกไปนั้นหายไปทางกายภาพจริงๆ และ SanitizeDocument จะล้างเมทาดาตา สคริปต์ และการดำเนินการต่างๆ ที่ข้อมูลที่ถูกปกปิดอาจซ่อนอยู่ หากทำขั้นตอนใดขั้นตอนหนึ่งผิดพลาด คุณจะส่งมอบไฟล์ที่ดูเหมือนถูกเซ็นเซอร์บนหน้าจอ แต่กลับส่งความลับคืนให้กับใครก็ตามที่คัดลอกข้อความหรือเปิดดูคุณสมบัติของเอกสาร (Document Properties)

ความล้มเหลวในลักษณะนี้มีบันทึกเป็นสถิติสาธารณะ หนังสือพิมพ์ ศาล และหน่วยงานภาครัฐต่างเคยเผยแพร่ไฟล์ PDF ที่มีกล่องสี่เหลี่ยมสีดำวางทับชื่อหรือตัวเลข แต่ผู้อ่านกลับสามารถเลือกข้อความที่อยู่ด้านล่าง คัดลอก และวางข้อความที่ควรจะถูกปกปิดนั้นให้ออกมาแสดงให้เห็นได้อย่างชัดเจน สี่เหลี่ยมสีดำนั้นเป็นเพียงภาพวาด แต่ข้อความยังคงอยู่ โดยซ่อนอยู่ในสตรีมเนื้อหาซึ่งอยู่ต่ำลงไปหนึ่งเลเยอร์ใต้สีที่ทาทับ มีสภาพสมบูรณ์และสามารถเลือกได้เต็มที่ losLab PDF Library ซึ่งเป็นเอนจิน PDF ของ Delphi และ C++Builder ที่ใช้งานผ่านคลาส TPDFlib ถือว่าการแยกแยะความแตกต่างนี้คือหัวใจสำคัญของการปกปิดข้อมูล ไม่ใช่เรื่องที่คิดได้ในภายหลัง

เหตุใดการวาดกล่องสีดำทับข้อความ PDF จึงไม่ใช่การปกปิดข้อมูลที่แท้จริง?

losLab PDF Library มีเมธอด MaskRect สำหรับกรณีที่การปิดบังคือสิ่งที่คุณต้องการจริงๆ เช่น ลายน้ำ, การปิดทับเพื่อพรางตา หรือตราประทับรับรอง โดย MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) จะเพิ่มรูปสี่เหลี่ยมผืนผ้าเข้าไปในสตรีมเนื้อหาของหน้าเว็บ และระบายสีด้วยตัวดำเนินการสร้างพาธ re ตามด้วยตัวดำเนินการเติมสี f (ISO 32000-1 §8.5) ทว่าทุกไบต์ที่อยู่บนหน้าเว็บอยู่แล้ว — ไม่ว่าจะเป็นรูปอักขระ (glyphs) ของชื่อที่คุณปิดทับ, เส้นเวกเตอร์ หรือรูปภาพฝังตัว — จะยังคงอยู่ที่เดิมทุกประการในฐานะคำสั่งก่อนหน้าในสตรีมเดียวกัน โปรแกรมเปิดอ่าน PDF ที่ทำงานตามมาตรฐานจะวาดกล่องสีดำนี้เป็นลำดับสุดท้าย ทำให้เห็นกล่องทับอยู่ด้านบน แต่การปิดทับนี้ไม่ได้เปลี่ยนข้อมูลด้านล่างเลย

ใครก็ตามสามารถดึงเนื้อหาต้นฉบับกลับมาได้สามวิธี: เลือกและคัดลอกข้อความผ่านกล่องทึบแสงโดยตรง, ใช้เครื่องมือดึงข้อมูลข้อความบนหน้ากระดาษ หรือลบสี่เหลี่ยมที่ปิดทับออกจากสตรีมเนื้อหา เมธอด MaskRect มีชื่อที่แสดงหน้าที่ตรงตัวคือ — มันทำหน้าที่บดบัง (mask) แต่ไม่ได้ลบออก (remove) ผลลัพธ์จึงเปรียบเสมือนสติกเกอร์ที่แปะทับคำศัพท์ซึ่งคุณยังคงอ่านได้เพียงแค่ลอกสติกเกอร์ออก

RedactRegion ลบเนื้อหาในระดับคำสั่งอย่างไร

RedactRegion(Page, Left, Top, Width, Height, Options) ทำงานในระดับที่ลึกลงไปใต้สีทาทับหนึ่งเลเยอร์ losLab PDF Library จะวิเคราะห์เลเยอร์เนื้อหาแต่ละชั้นของหน้าเว็บให้เป็น TPDFContentProgram ไล่ดูรายการคำสั่งด้วยตัววิเคราะห์ที่คอยติดตาม CTM (คือ FindInstructionsInRect ในโมเดลเนื้อหา) และทำเครื่องหมายทุกตัวดำเนินการที่แสดงข้อความ, วาดพาธ และภาพ Do ที่พื้นที่วาดตัดกับสี่เหลี่ยมผืนผ้าของคุณ คำสั่งเหล่านั้นจะถูกลบออกด้วย TPDFContentProgram.Delete และเลเยอร์ที่ได้รับการแก้ไขจะถูกเขียนกลับคืนด้วย WritePageContentLayer ไบต์ข้อมูลจะไม่ถูกเลื่อนไปซ่อนหลังสิ่งปิดบัง แต่จะถูกลบออกจากสตรีมไปเลย เครื่องมือดึงข้อความที่ทำงานหลังจากนั้นจะไม่พบอะไรเลย เนื่องจากไม่มีข้อมูลเหลืออยู่แล้ว

รายละเอียดการทำงานสองประการเป็นสิ่งที่ควรนำมาพิจารณา RedactRegion รับค่าพิกัดรูปสี่เหลี่ยมมุมบนซ้ายในจุดกำเนิดและหน่วยปัจจุบัน แล้วแปลงภายในให้เป็นพื้นที่ผู้ใช้มุมล่างซ้ายที่สตรีมเนื้อหาใช้งาน ดังนั้นคุณจึงระบุพื้นที่ตามที่คุณเห็นบนหน้ากระดาษได้เลย เมื่อรูปสี่เหลี่ยมผืนผ้าครอบคลุมหลายคำสั่ง RedactRegion จะลบคำสั่งเหล่านั้นตามลำดับดัชนีจากมากไปน้อย เนื่องจากหากลบคำสั่งที่ 4 ก่อนคำสั่งที่ 7 จะทำให้ดัชนีถัดไปทั้งหมดเลื่อนตำแหน่งและทำให้ช่วงดัชนีผิดพลาด หากกำหนดค่า Options = 0 ตัว losLab PDF Library จะประทับกล่องสีดำทึบลงบนพื้นที่ที่ถูกลบเพื่อเป็นเครื่องหมายระบุให้เห็น — แต่เครื่องหมายนั้นเป็นเพียงความสวยงามภายนอกที่ใส่เข้ามาหลังจากเนื้อหาจริงถูกลบออกไปแล้วเท่านั้น

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Delete everything painted inside a 220 x 14 pt box on page 1.
      // Coordinates are top-left, in the current origin and units.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 stamps a black marker
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

ข้อมูลที่ถูกปกปิดยังสามารถซ่อนอยู่ที่ใดใน PDF ได้อีก?

การปกปิดข้อมูลที่หยุดอยู่แค่เลเยอร์ที่มองเห็นจะยังคงเหลือร่องรอยเอาไว้ เนื่องจาก PDF จัดเก็บข้อมูลในตำแหน่งที่ผู้อ่านไม่เคยเปิดดู losLab PDF Library จัดหมวดหมู่ช่องทางที่ข้อมูลอาจรั่วไหลเหล่านี้ไว้เป็นส่วนๆ ได้แก่ Document Information Dictionary และสตรีมเมทาดาตา XMP มักจะเก็บสตริงของ Author, Producer หรือ Keywords ที่ระบุชื่อบุคคลหรือชื่อเรื่องที่คุณลบออกจากเนื้อหาไปแล้ว สคริปต์ JavaScript ระดับเอกสารและการดำเนินการ OpenAction ของแค็ตตาล็อกสามารถสั่งทำงานเมื่อเปิดเอกสารและเข้าถึงข้อมูลที่คุณคิดว่าถูกลบไปแล้วได้ นอกจากนี้คำอธิบายประกอบ (annotation) ทุกตัวยังสามารถเก็บการดำเนินการ /A และตัวคำอธิบายประกอบเอง — เช่น บันทึกย่อช่วยเตือน, ฟิลด์ฟอร์มเก่า หรือลิงก์ — สามารถวางทับตำแหน่งที่ถูกปกปิดโดยเก็บข้อความซ้ำอีกชุดหนึ่งไว้ ข้อมูลเหล่านี้ไม่ได้รับผลกระทบจากการเข้ารหัส ไฟล์ที่เข้ารหัสแต่มีแพ็กเก็ต XMP ที่เป็นข้อความธรรมดาจะยังคงทำให้ชื่อเรื่องรั่วไหลไปยังโปรแกรมจัดทำดัชนี ซึ่งเป็นช่องว่างที่ควรค่าแก่การ auditing encryption and permissions ด้วยตนเอง การทำความสะอาดที่ปลอดภัยและป้องกันได้จึงต้องตรวจสอบพื้นที่ซ่อนเหล่านี้ทั้งหมด ไม่ใช่แค่หน้าเอกสารที่คุณมองเห็น

การทำความสะอาดเอกสารด้วย SanitizeDocument

losLab PDF Library แก้ปัญหาการรั่วไหลเหล่านั้นด้วย SanitizeDocument(Flags) ซึ่งเป็นการเรียกคำสั่งที่สั่งทำงานชุดการลบที่เป็นอิสระต่อกันที่เลือกโดยบิตมาสก์ (bitmask) และส่งคืนจำนวนการลบที่ได้ดำเนินการไป แฟล็กต่างๆ สามารถนำมารวมกันได้ โดย SANITIZE_JAVASCRIPT จะลบแพ็กเกจ JavaScript ระดับเอกสารและสคริปต์ OpenAction ทั้งหมดออก SANITIZE_ACTIONS จะลบพจนานุกรมการดำเนินการ /A ออกจากคำอธิบายประกอบทุกตัวผ่าน TPDFAnnots.RemoveAnnotAction และล้าง OpenAction ของแค็ตตาล็อก SANITIZE_METADATA จะล้างข้อมูล Author, Subject, Keywords, Creator และ Producer ทั้งใน Document Information Dictionary (ISO 32000-1 §14.3.3) และสตรีมเมทาดาตา XMP (§14.3.2) SANITIZE_ANNOTATIONS จะลบคำอธิบายประกอบทั้งหมดทั่วทั้งเอกสาร และ SANITIZE_OPEN_ACTION จะลบรายการ /OpenAction ของแค็ตตาล็อกออก SANITIZE_ALL คือผลรวมบิตไวด์ (bitwise union) ของทั้งห้าแฟล็กนี้

ข้อกำหนดเรื่องดัชนีภายในเมธอดเหล่านี้มักเป็นสิ่งที่ทำให้ลูปที่เขียนขึ้นเองทำงานผิดพลาดเงียบๆ ซึ่งเป็นเหตุผลหลักที่ SanitizeDocument ถูกสร้างขึ้นมาให้เรียกใช้ในครั้งเดียว GlobalJavaScriptPackageName เป็นแบบอิงตาม 0 (0-based) และ RemoveGlobalJavaScript จะทำให้รายการหดสั้นลงเรื่อยๆ เมื่อทำงานไป ดังนั้นส่วนงานลบ JavaScript จะอ่านดัชนีแพ็กเกจที่ 0 เสมอและลบไปเรื่อยๆ จนกว่าจำนวนจะเป็นศูนย์ ในทางตรงกันข้าม DeleteAnnotation จะอิงตาม 1 (1-based) และ AnnotationCount จะรายงานผลเทียบกับหน้าเว็บที่เลือกอยู่ในปัจจุบัน ดังนั้นส่วนงานลบคำอธิบายประกอบจะเลือกแต่ละหน้าตามลำดับก่อนเริ่มนับจำนวน losLab PDF Library จัดการขอบเขตเหล่านี้อย่างถูกต้องภายในตัวมันเอง ดังนั้นคุณจึงเพียงแค่ส่งชุดแฟล็กและอ่านจำนวนที่ระบบดำเนินการกลับมา แทนที่จะต้องมาเขียนกฎการวนซ้ำที่แตกต่างกันถึงห้าแบบเอง

var
  Applied: Integer;
begin
  // Strip metadata, scripts, and actions but keep the annotations:
  Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
    SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);

  // Or clear every hidden channel, annotations included:
  Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;

เวิร์กโฟลว์การปกปิดข้อมูลและทำความสะอาดที่สมบูรณ์

เมื่อนำทั้งสองกระบวนการมารวมกัน ขั้นตอนทั้งหมดจะสั้นลง losLab PDF Library จะลบเนื้อหาที่มองเห็นด้วย RedactRegion ทีละหน้า จากนั้นล้างช่องทางที่ซ่อนอยู่ทั้งหมดด้วย SanitizeDocument(SANITIZE_ALL) ก่อนที่จะเขียนไฟล์ลงดิสก์ เนื่องจากกระบวนการปกปิดข้อมูลมักจะรันบนเอกสารที่ส่งมาจากภายนอกที่คุณควบคุมไม่ได้ จึงควรโหลดไฟล์ผ่านพาธที่ปลอดภัยเป็นพิเศษ — ซึ่งเป็นลักษณะการทำงานเชิงป้องกันเดียวกับที่อธิบายไว้ใน parsing untrusted PDFs safely — เพื่อให้อินพุตที่ผิดรูปแบบล้มเหลวอย่างปลอดภัย แทนที่จะไปล้มเหลวในระหว่างกระบวนการปกปิดข้อมูล

var
  PDF: TPDFlib;
  Applied: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('incoming.pdf', '') = 1 then
    begin
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);       // delete the sensitive line
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // clear metadata, JS, actions, annots
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

ขีดจำกัดของการปกปิดข้อมูลในสตรีมเนื้อหา

RedactRegion ทำงานกับสตรีมเนื้อหา ซึ่งเป็นตำแหน่งที่ข้อความที่สร้างแบบดิจิทัลอาศัยอยู่ แต่จะไม่ใช่ตำแหน่งของข้อความที่ผ่านการสแกนมา เมื่อหน้ากระดาษเป็นภาพสแกน คำศัพท์ต่างๆ จะเป็นเพียงพิกเซลในรูปภาพ XObject ไม่ใช่ตัวดำเนินการข้อความ และ losLab PDF Library ไม่สามารถแกะชื่อออกจากกลางภาพแรสเตอร์ในลักษณะเดียวกับการลบรูปอักขระได้ การจัดการรูปภาพของไลบรารีนี้ได้รับการออกแบบมาอย่างระมัดระวังเป็นพิเศษ: รูปภาพใดๆ ที่มีตำแหน่งตัดกับรูปสี่เหลี่ยมจะถูกลบออกไปทั้งหมด ดังนั้นกล่องปกปิดข้อมูลบนหน้าสแกนจะลบภาพสแกนทั้งหมดไปด้วยแทนที่จะลบแค่บางส่วน การปกปิดพื้นที่ภายในรูปภาพหมายถึงการแปลงเป็นแรสเตอร์, วาดทับ และฝังภาพกลับเข้าไปใหม่ในระดับพิกเซล ซึ่งเป็นการทำงานที่แตกต่างจากการลบสตรีมเนื้อหา ข้อควรระวังเดียวกันนี้ยังใช้กับเลเยอร์ข้อความ OCR บนหน้าสแกนด้วย: RedactRegion จะลบตัวดำเนินการข้อความที่มันมองเห็น แต่รูปภาพด้านล่างจะยังไม่ถูกแตะต้องเว้นแต่รูปภาพ XObject ของมันจะตัดกับกล่องปกปิดเช่นกัน

ประเด็นปลีกย่อยสองประการช่วยให้มั่นใจในความถูกต้องของการทำงาน การทดสอบจุดตัดจะคำนวณความกว้างโดยประมาณของข้อความจากจำนวนอักขระและขนาดฟอนต์ แทนที่จะเป็นการวัดขนาดรูปอักขระอย่างแน่นอน ดังนั้นคำสั้นๆ ที่ตั้งค่าด้วยฟอนต์แบบสัดส่วน (proportional font) อาจเล็ดลอดกล่องสี่เหลี่ยมที่วาดไว้แคบเกินไปได้ — จึงควรให้เผื่อระยะขอบไว้เล็กน้อย นอกจากนี้การลบยังทำงานในระดับรายละเอียดของคำสั่ง (instruction-granular): RedactRegion จะลบตัวดำเนินการที่แสดงข้อความทั้งหมดออก ดังนั้นสี่เหลี่ยมผืนผ้าที่ครอบคลุมเพียงบางส่วนของคำสั่งหนึ่งสามารถลบอักขระข้างเคียงที่ใช้ตัวดำเนินการเดียวกันไปด้วยได้ ข้อจำกัดเหล่านี้ไม่ใช่เหตุผลให้ไม่เชื่อมั่นในกลไกนี้ แต่เป็นเหตุผลให้กำหนดขอบเขตของสี่เหลี่ยมผืนผ้าอย่างระมัดระวังและตรวจสอบผลลัพธ์ที่ได้

ระเบียบปฏิบัติที่ต้องทำในทุกเวิร์กโฟลว์การปกปิดข้อมูลคือการตรวจสอบความถูกต้อง ไม่ใช่ความไว้วางใจ ทำการปกปิดพื้นที่, ทำความสะอาดเอกสาร, บันทึกเป็นไฟล์ใหม่, เปิดใหม่อีกครั้ง และลองดึงข้อมูลทั้งข้อความที่คุณลบออกและเมทาดาตาที่คุณล้างกลับมา หากทั้งสองส่วนกลับมาเป็นค่าว่าง แสดงว่าการปกปิดข้อมูลประสบความสำเร็จ API ของ RedactRegion และ SanitizeDocument ที่แสดงที่นี่จัดส่งมาพร้อมกับ losLab PDF Library สำหรับ Delphi และ C++Builder พร้อมกับเอกสารอ้างอิงของแบบจำลองเนื้อหาและการทำความสะอาดที่สมบูรณ์