💡 مثال كامل يعمل متاح على GitHub:
qr-sign-password-protected-pdf-python
المقدمة
هناك نمط من ثلاث خطوات يلجأ إليه معظم الفرق عندما يتبين أن المستند الذي يحتاج إلى توقيع مشفر: فك تشفيره، توقيع النص الصريح، وإعادة تشفير النتيجة. هذا يعمل. لكنه يعني أيضًا أنه لبضع مئات من المللي ثانية توجد نسخة قابلة للقراءة من مستند محمي عمدًا في دليل مؤقت، وفي خط أنابيب مُدقق تكون تلك النافذة هي ما يُكتشف بدلاً من التوقيع.
توقيع PDF محمي هو قدرة GroupDocs.Signature للبايثون عبر .NET تتخطى تلك الخطوات الثلاث تمامًا: كلمة المرور تفتح المصدر في مكانه، يتم تطبيق التوقيع، وتُكتب النتيجة مرة أخرى محمية. تقارن هذه المقالة بين أربع مسارات لكلمة المرور - اثنان يعملان واثنان يفشلان عمدًا - وتغطي عقد الفشل الخاص بهذا الربط.
لماذا هذا مهم
معالجة كلمة المرور هي النقطة التي تتسرب فيها خطوط أنابيب المستندات. ليس عبر مكتبة التوقيع عادةً، بل عبر البنية المحيطة بها: ملف المؤقت الذي كان من المفترض حذفه، معالج الاستثناءات الذي ابتلع خطأ كلمة مرور خاطئة وأعاد المحاولة إلى الأبد، النسخة الموقعة التي تُسلم مع كلمة مرور لم يُخبر المستلم بها.
جميع الثلاثة لها نفس السبب الجذري، وهو أن كلمة المرور تُعامل كشيء يُزال من الطريق بدلاً من أن تكون جزءًا من العملية. LoadOptions و SaveOptions يعيدانها إلى العملية.
المتطلبات المسبقة
Python 3 و groupdocs-signature-net==26.1، بالإضافة إلى PDF محمي بكلمة مرور مستخدم. بدون ترخيص، تعمل المكتبة في وضع التقييم، والذي لا يزال يوقع لكنه يضيف نصه الخاص إلى الصفحة.
التثبيت
pip install groupdocs-signature-net==26.1
الطريقة 1 - الاحتفاظ بكلمة المرور الأصلية
الإعداد الافتراضي، وهو الذي يحتاج أقل قدر من الشيفرة. تُمرَّر كلمة المرور عبر LoadOptions، ولا يتم تمرير أي SaveOptions على الإطلاق:
load_options = LoadOptions()
load_options.password = password
options = _build_qr_options(qr_text)
with signature.Signature(source_path, load_options) as sign:
result = sign.sign(output_path, options)
return len(result.succeeded)
غياب SaveOptions هو ما يقوم بالعمل الفعلي هنا. use_original_password يكون افتراضيًا True، لذا تقوم GroupDocs بإعادة تطبيق كلمة مرور المصدر على الناتج الموقّع. لا توجد لحظة توجد فيها نسخة غير محمية، على القرص أو غير ذلك، وlen(result.succeeded) يُبلغ عن عدد التوقيعات التي كُتبت.
الطريقة 2 - إعادة تعيين كلمة المرور للنسخة الموقعة
عندما يُرسل المستند الموقّع إلى طرف آخر، فإن الإجراء المنطقي هو إعطاء النسخة اعتمادها الخاص وترك المصدر كما هو:
save_options = SaveOptions()
save_options.password = new_password
save_options.use_original_password = False
with signature.Signature(source_path, load_options) as sign:
result = sign.sign(output_path, options, save_options)
return len(result.succeeded)
كلتا سطري SaveOptions مطلوبة، وهذه هي التفاصيل التي تستحق التذكر: ضبط password مع ترك use_original_password على قيمته الافتراضية لا يحدث أي تأثير ملحوظ. العلامة تفوز، ويحتفظ الناتج بكلمة المرور القديمة، وتكتشف ذلك عندما يُبلغ المستلم أن كلمة المرور التي أرسلتها لا تعمل.
الطريقة 3 و 4 - الفشلان
المستند المشفر يستجيب بشكل مختلف لغياب كلمة المرور أو لكلمة مرور خاطئة، والفرق يستحق المعالجة.
try:
with signature.Signature(source_path) as sign:
sign.sign(output_path, options)
return ""
except RuntimeError as error:
return proxy_error_name(error)
هذا يُعيد PasswordRequiredException. إذا قُدمت كلمة مرور غير صحيحة بدلاً من ذلك، فإن نفس الشيفرة تُعيد IncorrectPasswordException. الأولى تعني طلب الاعتماد من المستخدم؛ والثانية تعني أن الاعتماد لديك قديم. معالج لا يستطيع التمييز بينهما سيستمر في إعادة محاولة كلمة مرور لن تعمل أبدًا.
عقد الفشل، ولماذا يتعطل الكود الواضح
هذا هو الجزء الذي يكلفك بعد ظهر إذا لم يحذرك أحد. الربط يُظهر PasswordRequiredException و IncorrectPasswordException و GroupDocsSignatureException كأسماء عارية لا ترث من BaseException. اكتب المعالج البديهي:
except IncorrectPasswordException:
...
ويُطلق بايثون الخطأ TypeError: catching classes that do not inherit from BaseException is not allowed. الخطأ الأصلي يختفي، ويُستبدل بواحد يشير إلى سطر except الخاص بك بدلاً من كلمة المرور. كتبت هذا المعالج بالضبط في المرة الأولى، والعشرون دقيقة التي قضيتها في قراءة الـ TypeError هي السبب في وجود هذا القسم.
ما يصل فعليًا هو RuntimeError تبدأ رسالته بـ Proxy error(<Name>): . تحليل هذا البادئة يستعيد السبب:
message = str(error)
marker = "Proxy error("
if not message.startswith(marker):
return ""
start = len(marker)
end = message.find(")", start)
if end < 0:
return ""
return message[start:end]
قم بالفروع بناءً على الاسم المعاد بدلاً من نص الرسالة، الذي يحمل مسارات الملفات ويتغير بين التشغيلات.
الفحص قبل التوقيع
هناك مسار خامس يستحق المعرفة، ولا يكتب أي شيء على الإطلاق. فتح المستند باستخدام LoadOptions واستدعاء
get_document_info يُعيد الصيغة، عدد الصفحات والحجم بينما يبقى الملف مشفرًا على القرص:
with signature.Signature(source_path, load_options) as sign:
info = sign.get_document_info()
return info.file_type.file_format, info.page_count, info.size
استخدامان له. عندما تأتي كلمة المرور من نموذج مستخدم، يتحقق هذا من الاعتماد في استدعاء بسيط بدلاً من أن يحدث ذلك في منتصف دفعة من مئتي مستند. وعندما لا يُسمح لخط أنابيب بتخزين النص الصريح مطلقًا، فإنه لا يزال يتيح لهذا الخط الإبلاغ عما يحمله - عدد الصفحات لسجل التدقيق، الأحجام للحصص - دون فك تشفير أي شيء.
مقارنة الطرق: متى تستخدم كل منها
| الطريقة | الأفضل لـ | المزايا الرئيسية | القيود |
|---|---|---|---|
| الاحتفاظ بكلمة المرور الأصلية | خطوط أنابيب توقع في المكان | بدون SaveOptions، لا يُكتب شيء بنص واضح | المستلم يحتاج كلمة مرور المصدر |
| إعادة تعيين كلمة المرور عند الحفظ | تسليم إلى طرف آخر | المصدر يحتفظ باعتماده، النسخة تحصل على اعتماد جديد | سطران من SaveOptions، من السهل ضبط واحد فقط |
| بدون كلمة مرور (يفشل) | إثبات العقد في الاختبارات | يفشل عند الفتح، لا يكتب شيئًا | ليس مسار توقيع |
| كلمة مرور خاطئة (يفشل) | تمييز اعتماد قديم | اسم استثناء مميز | ليس مسار توقيع |
هل قراءة النتيجة تستحق النداء الإضافي؟
نعم، لسببين. إعادة فتح الملف الموقّع باستخدام QrCodeVerifyOptions يثبت أن التوقيع صمد بعد الحفظ، وبما أن إعادة الفتح تتطلب توفير كلمة المرور، فإنها تثبت أيضًا أن الناتج لا يزال مشفرًا. عدد الصفر يكاد يكون دائمًا مشكلة ترخيص بدلاً من فشل توقيع - استدعاء sign يرفع استثناءً عندما يفشل فعليًا، لذا الصمت مع عدد صفر يشير إلى بناء غير مرخص.
ما تكلفة التحويل
لا شيء هيكلي. إذا كان الكود الخاص بك بالفعل يفك التشفير إلى ملف مؤقت، فإن التغيير هو حذف تلك الخطوة، نقل كلمة المرور إلى
LoadOptions، وإزالة استدعاء إعادة التشفير في النهاية - عادةً ما يكون ذلك خسارة صافية في الأسطر. استدعاء التوقيع نفسه لا
يتغير في الشكل، والناتج هو PDF موقّع بايتًا لبايت بنفس الحماية التي كان يحملها عند الدخول.
المكان الوحيد الذي يجب فحصه بعناية هو كود التنظيف. عادةً ما يحتوي خط أنابيب مبني حول فك التشفير-التوقيع-إعادة التشفير على كتلة finally
تحذف ملف المؤقت، ومتى ما اختفى ملف المؤقت تصبح تلك الكتلة تحذف مسارًا لم يعد موجودًا.
أفضل الممارسات
- اترك
use_original_passwordكما هو ما لم تكن تقوم بتدويره عمدًا؛ الإعداد الافتراضي هو الأكثر أمانًا. - حلل اسم الوكيل مرة واحدة، في دالة مساعدة، واستخدمه في الفروع في كل مكان آخر.
- تحقق من صحة كلمة المرور التي يقدمها المستخدم باستخدام
get_document_infoقبل بدء الدفعة، بحيث يكلف الاعتماد السيء استدعاءً بسيطًا واحدًا بدلاً من تشغيل نصف مكتمل. - لا تكتب الناتج الموقّع فوق مسار المصدر، حتى لا يترك الخطأ النسخة الأصلية غير قابلة للاسترداد.
الخلاصة
كلمة المرور ليست عائقًا يجب تجاوزه قبل التوقيع - إنها وسيط للعملية. افتح باستخدام LoadOptions، وحدد حماية الناتج باستخدام SaveOptions، حلل اسم الوكيل عندما يفشل شيء ما، وتحقق عبر كلمة المرور لاحقًا. العينة تشغّل جميع المسارات الأربعة مرة واحدة، لذا الفرق بينها يتطلب أمرًا واحدًا لرؤيته بدلاً من فقرة لتصديقها.