💡 Повний робочий приклад доступний на GitHub:
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
Метод 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 — Два випадки помилок
Зашифрований документ реагує по-різному на відсутність пароля та неправильний пароль, і ця різниця варта обробки.
Якщо зовсім не вказати 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, розберіть ім’я проксі, коли щось не вдається, і після цього перевірте через пароль. Приклад виконує всі чотири шляхи за один запуск, тому різницю між ними можна побачити однією командою, а не довгим абзацом.