מאמר טכני

השחרה (Redaction) אמיתית של PDF בדלפי: מחיקה וניקוי

השחרת (redaction) PDF אמיתית בדלפי פירושה מחיקת הבייטים שמתחת, ולא ציור מעליהם. losLab PDF Library משרטטת קו גבול זה עם שני ממשקי API:‏ RedactRegion עורכת את זרם התוכן של העמוד כך שהטקסט, הווקטורים והתמונות שהוסרו נעלמים פיזית, ו-SanitizeDocument מנקה את המטא-נתונים, התסריטים (scripts) והפעולות שבהם מידע מושחר עשוי להסתתר גם כן. טעות באחד משני החלקים הללו תגרום לך לשלוח קובץ שנראה מצונזר על המסך אך מחזיר את הסוד לכל מי שמעתיק את הטקסט או פותח את מאפייני המסמך

אופן הכשל הזה הוא נחלת הכלל. עיתונים, בתי משפט וסוכנויות ממשלתיות שחררו כולם קובצי PDF עם מלבן שחור המונח על גבי שם או מספר, רק כדי שהקורא יבחר את הטקסט שמתחת, יעתיק אותו וידביק את המחרוזת כביכול מושחרת לעיני כל. המלבן היה ציור. הטקסט עדיין היה שם, ישב בזרם התוכן שכבה אחת מתחת לצבע, שלם לחלוטין וניתן לבחירה מלאה. losLab PDF Library, מנוע ה-PDF לדלפי ו-C++Builder החשוף דרך מחלקת TPDFlib, מתייחס להבחנה זו כאל כל המטרה של ההשחרה ולא כאל מחשבה שלאחר מעשה

מדוע ציור תיבה שחורה מעל טקסט PDF אינו השחרה אמיתית?

losLab PDF Library חושפת את MaskRect עבור מקרים שבהם כיסוי הוא באמת מה שאתה רוצה: סימן מים, השחרה חזותית, חותמת הוכחה. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) מוסיפה מלבן לזרם התוכן של העמוד ומציירת אותו עם אופרטור בניית הנתיב re ולאחריו אופרטור המילוי f‏ (ISO 32000-1 §8.5). כל בית שכבר היה על העמוד — הגליפים של השם שכיסית, קווי הווקטור, התמונה המשולבת — נשאר בדיוק היכן שהיה, פקודה אחת קודם לכן באותו זרם. מציג תואם מצייר את התיבה השחורה אחרונה, כך שהיא מנצחת מבחינה חזותית. הכיסוי אינו משנה דבר מתחת

כל אחד יכול לאחזר את התוכן המקורי בשלוש דרכים: לבחור ולהעתיק את הטקסט ישירות דרך התיבה האטומה, להריץ חלץ טקסט (text extractor) על העמוד, או למחוק את המלבן המכסה מזרם התוכן. MaskRect כנה לגבי זה בשמו שלו — הוא מסתיר (masks), הוא אינו מסיר. התוצאה היא מדבקה מעל מילה שעדיין ניתן לקרוא על ידי הרמת המדבקה

כיצד RedactRegion מוחקת תוכן ברמת הפקודה

RedactRegion(Page, Left, Top, Width, Height, Options) עובדת שכבה אחת מתחת לצבע. losLab PDF Library מנתחת כל שכבת תוכן של העמוד לתוך TPDFContentProgram, עוברת על רשימת הפקודות עם מנתח העוקב אחר CTM‏ (FindInstructionsInRect במודל התוכן), ומסמנת כל אופרטור מציג טקסט, מצייר נתיב ותמונת Do שאזור הציור שלהם מצטלב com עם המלבן שלך. פקודות אלה מוסרות באמצעות TPDFContentProgram.Delete, והשכבה הערוכה נכתבת בחזרה עם WritePageContentLayer. הבייטים אינם מחליקים מאחורי כיסוי — הם עוזבים את הזרם. הרצת חלץ טקסט לאחר מכן לא תמצא דבר, מכיוון שאין מה למצוא

שני פרטי יישום ראויים להיכלל בשיקולים שלך. RedactRegion מקבלת מלבן שמאלי-עליון בראשית הצירים והיחידות הנוכחיים וממירה אותו פנימית למרחב המשתמש השמאלי-תחתון שבו משתמש זרם התוכן, כך שאתה מגדיר את האזור כפי שאתה רואה אותו בעמוד. כאשר מלבן מכסה מספר פקודות, RedactRegion מוחקת אותן בסדר אינדקס יורד, מכיוון שהסרת פקודה 4 לפני פקודה 7 תסיט כל אינדקס מאוחר יותר ותשחית את הטווח. העבר Options = 0 ו-losLab PDF Library תחתים גם קופסה שחורה מלאה מעל האזור המנוקה כסימן חזותי — אך סימן זה הוא קוסמטי בלבד, המוחל לאחר שהתוכן האמיתי כבר נעלם

בפועל אתה ממעט לקבוע את המלבן בקוד קשיח. אתה מחפש בעמוד את המחרוזת הרגישה וקורא בחזרה את התיבה החוסמת (bounding box) שלה — אותו חיפוש עמוד ופירוט אלמנטים המכוסים ב-איתור טקסט ואלמנטים בעמוד בדלפי — ואז מוסר את המלבן הזה ל-RedactRegion:

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 של הקטלוג יכולים להריץ בעת הפתיחה ולהגיע לנתונים שחשבת שנעלמו. כל הערה נושאת פעולת /A אופציונלית, והערה עצמה — פתקית דביקה, שדה טופס מיושן, קישור — יכולה לשבת ישירות מעל הנקודה המושחרת עם עותק משלה של הטקסט. שום דבר מזה אינו מושפע מהצפנה; קובץ מוצפן עם חבילת XMP בטקסט פשוט עדיין מדליף את הכותרת שלו לכל מאנדקס, פער ששווה ביקורת הצפנה והרשאות בפני עצמו. ניקוי ראוי להגנה חייב לבקר בכל אחד ממקומות המסתור הללו, ולאח רק בעמוד שבו הסתכלת

ניקוי המסמך עם SanitizeDocument

losLab PDF Library נותנת מענה למגוון הדליפות הזה באמצעות SanitizeDocument(Flags), קריאת תזמור המריצה קבוצה של פעולות ניקוי עצמאיות הנבחרות על ידי מסיכת ביטים ומחזירה את מספר הפעולות שהוחלו. הדגלים ניתנים לשילוב. SANITIZE_JAVASCRIPT מסיר כל חבילת JavaScript ברמת המסמך וכל תסריט OpenAction.‏ SANITIZE_ACTIONS מסיר את מילון הפעולות /A מכל הערה, באמצעות TPDFAnnots.RemoveAnnotAction החדש, ומנקה את OpenAction של הקטלוג. SANITIZE_METADATA מרוקן את השדות Author,‏ Subject,‏ Keywords,‏ Creator ו-Producer הן במילון פרטי המסמך (ISO 32000-1 §14.3.3) והן בזרם המטא-נתונים XMP‏ (§14.3.2).‏ SANITIZE_ANNOTATIONS מוחק כל הערה לרוחב המסמך כולו, ו-SANITIZE_OPEN_ACTION מסיר את הרשומה /OpenAction של הקטלוג. SANITIZE_ALL הוא איחוד הביטים של כל החמישה

מוסכמות האינדקס בתוך פעולות אלה הן בדיוק מסוג הדברים ששוברים בשקט לולאות שנכתבו ידנית, וזו חלק גדול מהסיבה ש-SanitizeDocument קיימת כקריאה אחת. GlobalJavaScriptPackageName מבוסס 0, ו-RemoveGlobalJavaScript מכווץ את הרשימה תוך כדי תנועה, כך שפעולת ה-JavaScript תמיד קוראת את אינדקס החבילה 0 ומסירה עד שהספירה מגיעה לאפס. DeleteAnnotation, לעומת זאת, מבוסס 1.‏ AnnotationCount מדווחת מול העמוד שנבחר כעת, ולכן פעולות ההערות בוחרות כל עמוד בתורו לפני הספירה. losLab PDF Library מגדירה גבולות אלה נכון פנימית, כך שאתה מעביר סט דגלים אחד וקורא ספירה אחת בחזרה במקום לשחזר חמישה כללי איטרציה שונים

זרימת העבודה המלאה להשחרה וניקוי

שלב את השניים יחד והתהליך המלא קצר. losLab PDF Library מסירה את התוכן הגלוי עם RedactRegion, עמוד אחר עמוד, ואז מנקה כל ערוץ נסתר עם SanitizeDocument(SANITIZE_ALL) לפני שהקובץ נכתב. מכיוון שהשחרה מתבצעת כמעט תמיד על מסמכים שהגיעו מחוץ לשליטתך, כדאי לטעון אותם דרך נתיב מוקשח — אותה גישה הגנתית המתוארת ב-ניתוח בטוח של קובצי PDF ממקור לא מהימן — כך שקלט פגום ייכשל בצורה נקייה ולא באמצע תהליך ההשחרה

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 ולא אופרטורים של טקסט, ו-I-losLab PDF Library אינה יכולה לחצוב שם מתוך אמצע הרסטר באותו אופן שבו היא מוחקת גליף. הטיפול שלה בתמונות הוא שמרני בכוונה: כל תמונה שמיקומה מצטלב עם המלבן מוסרת במלואה, כך שתיבת השחרה בעמוד סרוק לוקחת את הסריקה כולה איתה ולא רק חלק ממנה. השחרת אזור בתוך תמונה פירושה רסטריזציה, ציור והטמעה מחדש ברמת הפיקסלים, וזו פעולה שונה ממחיקת זרם תוכן. אותה זהירות חלה על שכבת טקסט OCR מעל עמוד סרוק: RedactRegion מסירה את אופרטורי הטקסט שהיא יכולה לראות, אך התמונה שמתחת נותרת ללא שינוי אלא אם כן תמונת ה-XObject שלה מצטלבעם גם היא עם התיבה

שתי נקודות עדינות שומרות על אמינות התוצאות. בדיקת ההצטלבות מעריכה את הרוחב של כל ריצת טקסט לפי ספירת התווים וגודל הגופן שלה במקום למדוד מדדי גליפים מדויקים, כך שמילה קצרה מאוד בגופן פרופורציונלי יכולה לחמוק ממלבן מצויר היטב — תן לתיבה שוליים קטנים. מחיקה היא גם ברמת פירוט הפקודה: RedactRegion מסירה אופרטורים שלמים של הצגת טקסט, כך שמלבן שגוזר רק חלק מריצה יכול לקחת תווים סמוכים שחלקו איתה את אותו אופרטור. אף מגבלה אינה סיבה לחשוד במנגנון, אלא רק סיבה לתחום את המלבן בזהירות ולבדוק את התוצאה

המשמעת שחותמת כל זרימת עבודה של השחרה היא אימות, לא אמון. השחר את האזור, נקה את המסמך, שמור לקובץ חדש, פתח אותו מחדש ונסה למשוך בחזרה הן את הטקסט שהסרת והן את המטא-נתונים שניקית; כאשר שניהם חוזרים ריקים, ההשחרה החזיקה מעמד. ממשקי ה-API RedactRegion ו-SanitizeDocument המוצגים כאן מגיעים עם losLab PDF Library עבור דלפי ו-C++Builder, לצד רפרנס מלא של מודל התוכן והניקוי