כיצד לעבוד עם מנוע הצפנת PDF ב-Python

כיצד לעבוד עם מנוע הצפנת PDF ב-Python

Aspose.PDF קוד פתוח עבור Python (import aspose_pdf) מיישם הגנת סיסמת PDF ברמת הבייט דרך מחלקת מנוע פנימית קטנה, EncryptionUtils, ב-aspose_pdf.engine.encryption. כאן מתמקמים האלגוריתם AES-CBC והאלגוריתמים של נגזרת המפתחות של PDF Standard Security Handler (גרסאות ISO 32000 2 עד 6), באופן עצמאי משכבת Document API ברמה הגבוהה. מדריך זה מתקין את החבילה ומדריך שלושה משימות של המנוע: הצפנה ופענוח של בתים גולמיים עם AES-CBC, יצירת מפתחות Standard Security Handler עבור סיסמה (גרסאות 2-4), ויצירת מפתחות AES-256 עבור מסמכי גרסה 5/6.

מדריך שלב-אחר-שלב

שלב 2: ייבא את המחלקות הדרושות

EncryptionUtils אינו מיוצא מחדש מהחבילה ברמת העל aspose_pdf — ייבא אותו ישירות מתת-מודול engine.encryption:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

שלב 3: הצפנה ופענוח נתונים עם AES-CBC

encrypt_aes_cbc ו-decrypt_aes_cbc פועלים ישירות על כל מפתח של 16, 24 או 32 בתים (AES-128, AES-192 או AES-256) מבלי לעבור דרך נגזרת מפתח מבוססת סיסמה. encrypt_aes_cbc מיישם ריפוד PKCS7 פנימית, ו-decrypt_aes_cbc מסיר אותו.

key = os.urandom(32)  # AES-256; 16- and 24-byte keys are also accepted
plaintext = b"Confidential PDF content"

ciphertext = EncryptionUtils.encrypt_aes_cbc(key, plaintext)
decrypted = EncryptionUtils.decrypt_aes_cbc(key, ciphertext)

assert decrypted == plaintext
print("Roundtrip OK, ciphertext length:", len(ciphertext))

שלב 4: הפקת מפתחות מנהל אבטחה סטנדרטיים עבור סיסמה (גרסאות 2-4)

compute_owner_key_v4 מחשב את ערך סיסמת הבעלים (O) לפי אלגוריתם 3.3, ו-compute_user_key_v4 מחשב את ערך סיסמת המשתמש (U) ואת מפתח הצפנת הקובץ לפי אלגוריתם 3.4/3.5. verify_password_v4 מנגזר מחדש את המפתח מסיסמה מועמדת כדי שתוכל לאשר שהיא פותחת את המסמך לפני שאתה סומך עליו.

owner_password = "owner-secret"
user_password = "user-secret"
key_length = 16   # 128-bit (AES/RC4); Revision 2 uses 5 (40-bit)
revision = 4
file_id = os.urandom(16)
permissions = -4  # standard PDF permissions value

o_value = EncryptionUtils.compute_owner_key_v4(
    owner_password, user_password, key_length, revision
)

u_value, encryption_key = EncryptionUtils.compute_user_key_v4(
    user_password, o_value, permissions, file_id, key_length, revision
)

verified_key = EncryptionUtils.verify_password_v4(
    user_password, u_value, o_value, permissions, file_id, key_length, revision
)
assert verified_key == encryption_key

ciphertext = EncryptionUtils.encrypt_aes_cbc(verified_key, b"Confidential PDF content")
decrypted = EncryptionUtils.decrypt_aes_cbc(verified_key, ciphertext)
assert decrypted == b"Confidential PDF content"

o_value ו-u_value תמיד באורך 32 בתים ללא תלות ב-key_length. verify_password_v4 מחזיר None — ולא חריגה — כאשר הסיסמה אינה תואמת.


שלב 5: הפקת מפתחות AES-256 עבור מסמכי גרסה 5/6

מנהלי האבטחה של גרסאות 5 ו-6 (AES-256) אינם משתמשים ב-compute_owner_key_v4/compute_user_key_v4 — הם מבצעים hash לסיסמה עם compute_hash_v5, המיישם את “אלגוריתם 2.B” של ISO 32000-2. השתמש ב-generate_file_id למזהה קובץ חדש, וב-derive_object_key כדי להפוך מפתח הצפנת קובץ למפתח עבור אובייקט עקיף ספציפי אחד.

password = b"SecurePassword123"
salt = os.urandom(8)

password_hash = EncryptionUtils.compute_hash_v5(password, salt)
assert len(password_hash) == 32

file_id = EncryptionUtils.generate_file_id()
assert len(file_id) == 16

file_key = os.urandom(32)
object_key = EncryptionUtils.derive_object_key(file_key, 12, 0, use_aes=True)
assert len(object_key) == 16

derive_object_key מערבב את מספר האובייקט ומספר הדור במפתח הקובץ, כך שכל אובייקט עקיף מקבל מפתח משלו למרות שמפתח הקובץ משותף.


בעיות נפוצות ותיקונים

encrypt_aes_cbc/decrypt_aes_cbc מעלה את השגיאה “AES key must be 16, 24, or 32 bytes”. הארגומנט key חייב להיות מפתח AES אמיתי, ולא מחרוזת סיסמה גולמית. צור אחד באמצעות os.urandom(16 | 24 | 32), או לגזור אחד תחילה באמצעות compute_user_key_v4 (Revisions 2-4) או compute_hash_v5 (תיקונים 5/6).

verify_password_v4 returns None במקום לזרוק. זה אומר שהסיסמה לא תואמת — זה אינו תנאי שגיאה לתפוס, רק תוצאה לבדיקה. גם יש לאשר ש permissions, file_id, key_length, ו revision הם אותם ערכים מדויקים שבהם השתמשו כאשר המסמך o_value and u_value חושבו במקור; חוסר התאמה באחד מהם גורמת בשקט למפתח השגוי במקום להיכשל בקול רם.

compute_owner_key_v4 עם סיסמת בעלים ריקה. Passing "" for owner_password זהו התנהגות המוגדרת במפרט, לא באג — האלגוריתם חוזר להשתמש בסיסמת המשתמש, ולכן סיסמאות הבעלים והמשתמש נעשות שוות.

מפתחות שנוצרו עבור גרסה שגויה או אורך מפתח שגוי. גרסה 2 משתמשת במפתחות RC4 של 40-bit (5-byte), גרסה 3/4 משתמשות במפתחות RC4/AES של 128-bit (16-byte), וגרסה 5/6 משתמשות compute_hash_v5 במקום compute_owner_key_v4/compute_user_key_v4 בהחלט. ערבוב של אלה לא גורם לשגיאה — הוא רק יוצר מפתח שלא יפתח את המסמך.

compute_hash_v5 התפוקה משתנה באופן בלתי צפוי. הפרמטר השלישי (user_key) מתערבב עם ההאש. העבר b"" בצורה מפורשת כשאתה רוצה האש אימות סיסמה פשוט, מכיוון שהשמטת זאת אינה זהה להעברת ערך ריק בכל מקום שבו הפונקציה נקראת.


שאלות נפוצות

למה EncryptionUtils אינו ניתן לייבוא מ-aspose_pdf ישירות?

הוא נמצא בתת-חבילה engine מכיוון שהוא פרט יישום פנימי של הגנה על מסמכים ברמת סיסמה, ולא חלק מהממשק הציבורי ברמה גבוהה של API. ייבא אותו במפורש מ-aspose_pdf.engine.encryption.

האם compute_hash_v5 זהה לקריאה בודדת של SHA-256?

לא. היא מיישמת את האלגוריתם 2.B של ISO 32000-2, שמבצע סבבים מרובים באמצעות אלגוריתמי hash שונים שנבחרים לפי ערכים ביניים — באופן מודע יקר יותר משיחה בודדת של hash.

האם generate_file_id() מחזיר את אותם הבייטים בכל פעם?

לא — היא מחזירה ערך רענן ואקראי של 16-בייט בכל קריאה. מזהה קובץ שונה משנה כל מפתח שמופק ממנו, אפילו כאשר הסיסמה זהה.

האם כל האובייקטים העקיפים ב-PDF מוצפן חולקים מפתח אחד?

לא. derive_object_key משלב את מספר האובייקט ומספר הדור עם מפתח ההצפנה של הקובץ, ולכן לכל אובייקט עקיף יש מפתח ייחודי משלו.

האם קוד האפליקציה צריך לקרוא ל-EncryptionUtils ישירות כדי להגן על PDF עם סיסמה?

בדרך כלל לא. EncryptionUtils הוא המנוע ברמה הנמוכה שעליו נבנות ה-API של סיסמאות ברמת המסמך. רוב האפליקציות צריכות להגן על קבצי PDF דרך API ברמת המסמך ופונות ל-EncryptionUtils רק לצורך כלי מותאם אישית או חקירה פורנזית של קבצים מוצפנים.


ראה גם

 עברית