דגל הרשאה אינו מנגנון אבטחה. הסיבית שאומרת "אין העתקה" חיה בתוך אותו מילון /Encrypt כמו ההצפנה, מה שמעניק לה אווירת אכיפה שאין לה, וברגע שתתייחסו לשניהם כדבר אחד הביקורת שלכם תתחיל לייצר תשובות שגויות. השאלה היחידה שכדאי לשאול על PDF אינה "האם הוא מוצפן". היא ספציפית יותר וקשה יותר: איזה אלגוריתם, איזו גרסה של מנהל האבטחה, איזו מהשתי סיסמאות הוגדרה, אילו סיביות הרשאה נטענות, ואילו חלקים של הקובץ ההצפנה בפועל נוגעת בהם. קובץ יכול להיות מוצפן פורמלית ופתוח מעשית. הוא יכול לסרב להיקרא אך להשאיר את המטא-דאטה שלו בטקסט רגיל. הוא יכול לנעול הדפסה בדגל שכל צופה חופשי להתעלם ממנו. ביקורת של PDF אומרת לפתור את כל אלה בנפרד, ו-PDFlibPas, מנוע ה-PDF של losLab עבור Delphi ו-C++Builder, חושף כל אחד מהם דרך API שטוח עם מזהה שלם וגם דרך שכבת מחלקה עם טיפוסים
מה מילון /Encrypt בפועל מתעד
ISO 32000-1 §7.6 מגדיר אבטחת מסמך דרך קומץ ערכי מילון, ו-PDFlibPas משקף אותם אחד-לאחד ברשומת TPDFEncryption. גרסת המסנן V והגרסייה R בוחרות את משפחת האלגוריתם. Length נושא את גודל המפתח. סיביות ההרשאה ממוקמות ב-P, מחרוזות אימות סיסמת בעל ומשתמש ב-O וב-U (עם OE ו-UE שנוספו עבור AES-256), דגל EncryptMetadata מגיע יחד עמהם, ושלושה שדות נוספים מציינים את מסנני ה-crypt שמוחלים על מחרוזות, זרמים וקבצים מוטבעים בהתאמה
הערך של רשומה זו הוא שהיא לא מפרשת דבר עבורכם. היא מחזירה את המילון הגולמי ומאפשרת לכם להסיק את המסקנות, וזה בדיוק מה שביקורת זקוקה לו. מקרה הטקסט-הרגיל-בתוך-המוצפן מופיע ב-StringFilterIdentity וב-StreamFilterIdentity: כאשר אחד מהם נכון, הנתונים המתאימים עוברים דרך מסנן Identity ללא שינוי, לא משנה מה דיווח סטטוס ההצפנה של המסמך. סורק שנעצר ב"מילון /Encrypt קיים" יקרא לקובץ כזה מוגן כאשר המחרוזות והזרמים שלו יושבים בטקסט רגיל. אותו ניואנס שולט במטא-דאטה. כאשר EncryptMetadata שקרי, מנת ה-XMP נשארת קריאה לכל מאנדקסר בעוד תוכן העמוד לא, מה שכדאי לדעת ברגע שכללי הניתוב שלכם מבוססים על שדה כותרת או מחבר
בדיקת אבטחה קצרה עם ה-API השטוח
עבור רוב הצינורות, ארבע קריאות שטוחות עונות על השאלות היומיומיות. LoadFromFile מחזיר 1 בהצלחה, וברגע שהמסמך פתוח בודקי ההצפנה מדווחים כנגד מצבו המפוענח:
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
raise Exception.Create('Open failed: wrong password or damaged file');
Writeln('status : ', PDF.EncryptionStatus); // decrypted / encrypted / unknown
Writeln('algorithm : ', PDF.EncryptionAlgorithm); // RC4 vs AES family
Writeln('strength : ', PDF.EncryptionStrength); // key length class
Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
finally
PDF.Free;
end;
end;
CheckPassword חשוב יותר ממה שחתימתו של שורה אחת מרמזת. PDF מגדיר שתי סיסמאות עם כוח לא שווה. סיסמת המשתמש נדרשת לפתיחת הקובץ כלל. סיסמת הבעל מעניקה זכויות מלאות ועוקפת כל סיבית הרשאה. הבתים על הדיסק זהים בכל מקרה, אך הפעלה פתוחה תחת סיסמת הבעל יכולה לעשות דברים שהפעלה עם סיסמת משתמש לא יכולה, לכן ביקורת שאינה מתעדת איזה אישורים הוצגו מתעדת חצי אמת. שכבת המחלקה הופכת את ההבחנה לניתנת לשאילה. TPDFDocument.HasUserPassword ו-HasOwnerPassword מדווחים מה הקובץ דורש, בעוד IsUserPassword ו-IsOwnerPassword מדווחים איזו סיסמה בפועל פתחה את ההפעלה הנוכחית. תעדו עובדה זו. לעולם אל תתעדו את ערכי הסיסמה עצמם
סולם ה-Strength, שבו "AES-256" אומר שני דברים
פונקציות Encrypt ו-EncryptFile השטוחות מקבלות integer של Strength עם חמישה ערכים משמעותיים: 0 עבור RC4 של 40 סיביות, 1 עבור RC4 של 128 סיביות, 2 עבור AES של 128 סיביות קריא מ-Acrobat 7, 3 עבור AES של 256 סיביות כפי שהוצג עם Acrobat 9, ו-4 עבור AES של 256 סיביות כנדרש על ידי Acrobat X ואילך
החלק המעניין הוא ש-3 ו-4 שניהם מסומנים AES-256 ואינם אותה תכנית. Strength 3 ממופה לגרסה 5 של מנהל האבטחה, עיצוב ביניים שנשלח ב-Acrobat 9 ו-ISO מעולם לא אימץ. Strength 4 ממופה לגרסה 6, שפונקציית גזירת המפתח שלה הוקשחה ותוקנה ב-ISO 32000-2. עבור מסמך שאתם יוצרים היום אין סיבה לבחור 3 על פני 4. עבור ביקורת הפער מכריע: מדיניות שקוראת "AES-256 לפי ISO 32000-2" מתקיימת על ידי R6 בלבד, וקובץ R5 שמכנה את עצמו AES-256 נכשל במדיניות זו בעוד עובר בדיקת strength נאיבית. שכבת המחלקה מפרידה בין השניים לפי שם, esAES256Bit עבור R5 מול esAES256BitAcroX עבור R6, ותכונת EncryptionAcroX עונה על שאלת הגרסייה עם בוליאני אחד
סיביות הרשאה והאותיות הקטנות של אורך המפתח שלהן
EncodePermissions ארוז שמונה דגלים ב-integer ש-Encrypt ו-EncryptFile מצפים לו. הדפסה, העתקה, שינוי והוספת הערות שוליים מהווים את הקבוצה הבסיסית; מילוי-שדות, העתקה-לנגישות, הרכבה והדפסה-באיכות-מלאה מהווים את הקבוצה המורחבת. האותיות הקטנות, שהדמו של ספריית ההצפנה עצמה מציין ישירות, הן שארבעת המורחבים נכנסים לתוקף רק בעוצמת 128 סיביות ומעלה. דגל ההדפסה-באיכות-מלאה כפוף לאותה כלל: נקו אותו כדי לאלץ הדפסה ברזולוציה נמוכה ומסמך של 40 סיביות יתעלם מכם, כיוון שגם שדרוג לאחור זה דורש הצפנה של 128 סיביות או חזקה יותר. קודדו מדיניות "הדפסה ברזולוציה נמוכה בלבד" לקובץ של 40 סיביות וכל צופה ידפיס באיכות מלאה בכל מקרה
השאלה העמוקה יותר היא מי אוכף כל אחד מהסיביות האלה, והתשובה היא אף אחד שאתם יכולים לסמוך עליו. הרשאות הן הוראות לקוראים תואמים, לא הגבלות קריפטוגרפיות. מפתח הפענוח זהה בין אם העתקה מותרת ובין אם היא מסורבת, כך שקבוצת הרשאות נעולה רק שומרת קוראים ישרים ישרים. קורא שבוחר להתעלם מהסיביות לא מתמודד עם שום מכשול קריפטוגרפי כלל. אם החובה היא למנוע חילוץ ולא רק להרתיע ממנו, הקובץ זקוק לסיסמת משתמש ולזרימת העבודה זקוקה לפקדי ברמת התהליך סביבה, ודוח ביקורת צריך לציין תחת איזה מהשני משטרים כל קובץ נמצא בפועל במקום להתייחס לדגל הרשאה כמנעול
הגדרת מדיניות והוכחה שנקלטה
החלת הצפנה על קבצים קיימים אינה דורשת טעינתם לעץ האובייקטים. EncryptFile מעבד קלט לפלט בקריאה אחת, ולולאת הביקורת פותחת מחדש את התוצאה כדי לאשר מה נחת על הדיסק. הדמו של ספרייה ההצפנה שנשלחת עוקב אחרי אותה צורה של כתיבה-ואז-קריאה-חזרה:
var
PDF: TPDFlib;
R: Integer;
begin
PDF := TPDFlib.Create;
try
R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
PDF.EncodePermissions(1, 0, 0, 0, // print allowed; copy/change/notes denied
0, 0, 0, 1)); // extended set: full-quality print only
if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
begin
Writeln('algorithm = ', PDF.EncryptionAlgorithm);
Writeln('strength = ', PDF.EncryptionStrength);
Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
end;
finally
PDF.Free;
end;
end;
צוותים שעובדים בשכבת המסמך מקבלים את אותה פעולה עם קבוצות מוקלדות במקום אריזת סיביות, שעוברת ביקורת קוד עם הרבה פחות עיגול עיניים:
if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
[ppCanPrint], [ppCanPrintFull]) then
raise Exception.Create('Encryption failed');
בכל מקרה, שלב הקריאה-חזרה אינו טקס רשות. הוא תופס את טעויות הפריסה שאחרת מתגלות חודשים מאוחר יותר במחשב של לקוח: בנייה ישנה של ספרייה שמורידה בשקט את העוצמה המבוקשת, נתיב פלט שלא נכתב מעולם כיוון שהספרייה הייתה לקריאה בלבד, integer הרשאות שהארגומנטים שלו הלכו בסדר הלא נכון. כל שלושתם עוברים בדיקת smoke מקומית ונכשלים בשטח, ופתיחה מחדש של הפלט הופכת כל אחד מהם לחריגה שאתם רואים במהלך הריצה שיצרה את הקובץ. GetEncryptionFingerprint מחזיר ערך קומפקטי שאתם יכולים לאחסן עם רשומת העבודה, כך שהשוואה מאוחרת יכולה לאמר אם שני פלטים חולקים את אותה קונפיגורציית הצפנה מבלי לפתוח אף אחד מהם
פוזיטיביים כוזבים בביקורת שכדאי לקודד עבורם
כמה דפוסים דוחפים באופן אמין סורקי אבטחה למסקנה השגויה, וכל אחד נובע מקריסת שאלה רב-חלקית לתשובה כן-או-לא. מסנן ה-crypt של Identity הוא הדוגמה הנקייה ביותר. מילון /Encrypt קיים, הקובץ מדווח כמוצפן, ועדיין המחרוזות והזרמים עוברים דרך מסנן Identity ללא שינוי, אז התוכן האמיתי הוא טקסט רגיל. קריאת StringFilterIdentity ו-StreamFilterIdentity לפני הכרזה על כל דבר כמוגן היא הפתרון
פיצול המטא-דאטה עדין יותר. EncryptMetadata יכול לחלוק עם שאר המסמך בשני הכיוונים, ומשאיר קובץ מוצפן עם מנת XMP קריאה, או, לעתים נדירות, ההיפך. "הקובץ מוצפן" לא אומר דבר על כך שהמטא-דאטה שלו מוצפנת, מה שחשוב ברגע שמאנדקסר או כלל ניתוב מגיע לכותרת. קבצים מוטבעים מוסיפים ציר שלישי: PDF מאפשר מסנן crypt ייעודי רק לקבצים מצורפים, כך שהקבצים המצורפים יכולים להיות החלק המוצפן היחיד של מסמך אחרת פתוח, או החלק הטקסט-הרגיל היחיד של מוצפן. צלמו את שלוש הקצאות המסנן כשדות נפרדים עבור מחרוזות, זרמים וקבצים מוטבעים, ואף אחת מהמלכודות האלה לא תתפוס אתכם. אחסנו בוליאני אחד והשיחה הלא-נכונה היא רק עניין של זמן
הסרת הצפנה, ובחירתה לקבצים חדשים
ביקורת לעתים קרובות מסתיימת בהחלטה לפשוט הגנה, והמכניקה אינה המכשול שם. DecryptFile(InputFileName, OutputFileName, Password) כותב עותק מפוענח ללא טעינה מלאה, וה-Decrypt של מסמך טעון עושה אותו הדבר בזיכרון ברגע שקובץ כבר פתוח. שניהם דורשים סיסמה תקפה; אף אחד לא עוקף את ההצפנה. השער האמיתי הוא מדיניות ולא קוד, אז ציינו בבירור בכללי הקבלה שלכם מתי הסרה מותרת ותעדו את מחלקת הסיסמה שאישרה אותה, כיוון שהצעד הטכני עצמו לא משאיר שום עקבות
הבחירה לפלט חדש צרה יותר מחמשת ערכי Strength מרמזים. השתמשו ב-Strength 4, AES-256 גרסייה 6, אלא אם כן אתם חייבים לפתוח קבצים בצופים ישנים יותר מ-Acrobat X. Strength 2, AES-128, הוא הרצפה הפרגמטית עבור צי צופים מיושן שלא ניתן לשדרג. האפשרויות RC4 ב-0 וב-1 קיימות כדי שתוכלו לקרוא ולבקר ארכיונים היסטוריים, לא כדי לייצר בהם משהו חדש; הגעה אליהם בעיצוב של 2026 היא סימן שדרישה במעלה הזרם מיושנת
מצב ההצפנה מוזן ישירות להחלטות חתימה, שכן סביבת עבודה שמאמתת וחותמת מסמכים זקוקה לאותה משמעת קריאה-חזרה שביקורת זו מסתמכת עליה. קרקע זו מכוסה במאמר סביבת עבודה לתאימות ולחתימה. כאשר אצווה מחילה EncryptFile על אלפי מסמכים גדולים, מדריך הגישה הישירה ל-PDF גדולים מציג כיצד לשמור על זיכרון שטוח בזמן שהיא רצה. הפניה המלאה ל-API של הצפנה נמצאת בדף המוצר PDFlibPas