💡 مثال کامل قابل اجرا در گیت‌هاب موجود است:
qr-sign-password-protected-pdf-python

مقدمه

الگوی سه‌مرحله‌ای که اکثر تیم‌ها وقتی سندی که نیاز به امضا دارد رمزگذاری شده باشد، به کار می‌برند این است: رمزگشایی، امضای متن ساده، دوباره رمزگذاری نتیجه. این روش کار می‌کند. اما به این معنی است که برای چند صد میلی‌ثانیه یک نسخه قابل خواندن از سندی که عمداً محافظت شده است، در یک پوشه موقت وجود دارد و در یک خط لولهٔ بازرسی‌شده همان بازه زمانی، نقطهٔ یافتن است نه امضا.

امضای PDF محافظت‌شده یک قابلیت GroupDocs.Signature برای Python از طریق .NET است که این سه مرحله را به‌طور کامل حذف می‌کند: رمز عبور منبع را در محل باز می‌کند، امضا اعمال می‌شود و خروجی به‌صورت محافظت‌شده بازنویسی می‌شود. این مقاله چهار مسیر رمز عبور را مقایسه می‌کند – دو مسیر که کار می‌کنند و دو مسیر که عمداً شکست می‌خورند – و قرارداد شکست خاص این بایندینگ را بررسی می‌کند.

چرا این مهم است

مدیریت رمز عبور جایی است که خطوط لولهٔ اسناد نشت می‌کنند. نه از طریق کتابخانهٔ امضا، معمولاً، بلکه از طریق چارچوب اطراف آن: فایل موقتی که باید حذف می‌شد، هندلر استثنایی که خطای رمز عبور اشتباه را قاپید و برای همیشه دوباره تلاش می‌کرد، نسخهٔ امضا شده‌ای که با رمزی که گیرنده هرگز از آن مطلع نشده بود، تحویل داده می‌شد.

هر سه مورد ریشهٔ یکسانی دارند: رمز عبور به‌عنوان چیزی که باید از مسیر برداشته شود در نظر گرفته می‌شود نه به‌عنوان بخشی از عملیات. LoadOptions و SaveOptions آن را دوباره به عملیات برمی‌گردانند.

پیش‌نیازها

Python 3 و groupdocs-signature-net==26.1، به‌اضافه یک PDF با رمز عبور کاربری. بدون لایسنس کتابخانه در حالت ارزیابی اجرا می‌شود که همچنان امضا می‌کند اما متن خود را به صفحه اضافه می‌کند.

نصب

pip install groupdocs-signature-net==26.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) تعداد امضاهای نوشته‌شده را گزارش می‌دهد.

روش ۲ - تغییر رمز عبور نسخهٔ امضا شده

وقتی سند امضا شده به طرف دیگری تحویل داده می‌شود، کار منطقی این است که برای نسخهٔ کپی، اعتبارنامهٔ خود را داشته باشید و منبع را دست‌نخورده بگذارید:

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 روی مقدار پیش‌فرض خود باقی می‌ماند، هیچ اثر قابل مشاهده‌ای ندارد. پرچم برتری دارد، خروجی رمز عبور قبلی را حفظ می‌کند و شما آن را وقتی دریافت‌کننده گزارش می‌کند که رمز عبوری که ارسال کرده‌اید کار نمی‌کند، کشف می‌کنید.

روش ۳ و ۴ - دو شکست

یک سند رمزگذاری‌شده به‌طور متفاوتی به عدم وجود رمز عبور و رمز عبور اشتباه واکنش نشان می‌دهد و این تفاوت ارزش مدیریت دارد.

بدون هیچ LoadOptions ای، باز کردن شکست می‌خورد و هیچ چیزی نوشته نمی‌شود:

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:
    ...

و Python خطای 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 تعیین کنید، وقتی چیزی شکست می‌خورد نام پروکسی را تجزیه کنید و پس از آن با رمز عبور تأیید کنید. نمونهٔ ارائه‌شده همهٔ چهار مسیر را در یک اجرا اجرا می‌کند، بنابراین تفاوت بین آن‌ها با یک فرمان قابل مشاهده است نه با یک پاراگراف برای اعتماد.

منابع تکمیلی