💡 Повний робочий приклад доступний на GitHub:
sign-docx-with-mldsa-certificates-python
Вступ
Підпишіть контракт сьогодні після обіду за допомогою RSA‑2048, і ви даєте обіцянку, яка має залишатися дійсною стільки, скільки важливий сам контракт. Якщо це двадцять‑тридцять років — а для актів, форм згоди та інженерних підписів це часто так — обіцянка повинна пережити алгоритм. Атака не обов’язково має існувати сьогодні. Вона має з’явитися до того, як документ перестане мати значення, і тоді будь‑хто, хто має публічний ключ, зможе відновити приватний і підписати від вашого імені.
Пост‑квантове підписання документів — це функція GroupDocs.Signature для Python, яка замінює цю обіцянку на підпис, побудований на ML‑DSA, алгоритмі підпису, стандартизованому NIST як FIPS 204 у 2024 році. Підтримка формату Word з’явилася у GroupDocs.Signature 26.9 і повторно використовує вже знайомий API: ключ ML‑DSA зберігається у PFX і передається в DigitalSignOptions точно так само, як RSA‑ключ.
У цьому посібнику підписується DOCX у чотири кроки, порівнюються три рівні безпеки за виміряним розміром, перевіряється підпис лише за допомогою публічного сертифікату і підсумовуються два обмеження, про які варто знати перед впровадженням.
Чому це важливіше, ніж звичайна міграція
Міграція підписів відрізняється від міграції шифрування однією рисою, яка полегшує її відкладення, але ускладнює виправлення.
У випадку шифрування проблема «збирай‑зараз‑розшифруй‑пізніше» є негайною: будь‑яке перехоплене сьогодні повідомлення можна зберегти і розшифрувати пізніше. У підписах нічого, що вже підписано, не стає підроблюваним заднім числом — але й підписане вже не залишається доведено вашим, коли ключ можна відновити з сертифікату, копію якого має кожен. Перепідписати десятиліття архівних документів новими ключами можливо, і ніхто не хоче планувати таку роботу.
Тому практична порада вузька, а не всеохопна: мігруйте документи з довгим терміном зберігання, інші залиште. Деякі профілі вже встановили планку — CNSA 2.0 вимагає ML‑DSA‑87 для систем національної безпеки — а для всіх інших вирішальним фактором є те, як довго файл має залишатися захищеним.
Передумови
- Python 3.9 або новіший на 64‑бітному інтерпретаторі — пакет постачається з вбудованим .NET‑runtime і не має 32‑бітного колеса
- GroupDocs.Signature для Python через .NET 26.10.0, з безкоштовною тимчасовою ліцензією для зняття обмежень оцінки
- Сертифікат ML‑DSA у вигляді захищеного паролем PFX та Word‑документ для підпису
Встановлення
pip install groupdocs-signature-net
Крок 1 — Підписання сертифікатом ML‑DSA
Сертифікат виконує всю роботу. Виклик такий самий, як і для RSA:
with signature.Signature(source_path) as sign:
options = DigitalSignOptions(pfx_path)
options.password = CERTIFICATE_PASSWORD
result = sign.sign(output_path, options)
Це вся історія впровадження для коду, який вже підписує: просто вкажіть DigitalSignOptions інший PFX. Немає нових параметрів, окремого параметра алгоритму чи гілки для пост‑квантового.
Зчитування підпису вимагає ще одного кроку і містить одну специфічну для Python пастку:
for created in result.succeeded:
certificate = getattr(created, "certificate", None)
subject = getattr(certificate, "subject", None)
if subject:
return str(subject)
Сертифікат у DigitalSignature — це об’єкт‑мост, який динамічно розв’язує атрибути. certificate.subject повертає CN=GroupDocs.Signature MLDSA65 test, тоді як dir() на тому ж об’єкті нічого не виводить. Я спочатку дослідив його за допомогою dir(), зробив висновок, що атрибут не доступний, і помилково вважав це помилкою — тому якщо ви інспектуєте перед читанням, пропустите існуюче значення.
Крок 2 — Порівняння трьох рівнів безпеки
ML‑DSA має три набори параметрів, які вибираються передачею різного сертифікату:
levels = (
("ML-DSA-44", MLDSA44_PFX),
("ML-DSA-65", MLDSA65_PFX),
("ML-DSA-87", MLDSA87_PFX),
)
for level, pfx_path in levels:
with signature.Signature(source_path) as sign:
options = DigitalSignOptions(pfx_path)
options.password = CERTIFICATE_PASSWORD
sign.sign(output_path, options)
sizes[f"{level} -> {file_name}"] = os.path.getsize(output_path)
Цей крок варто виконати, бо компроміс зазвичай описується, а рідко вимірюється. На прикладі 132 KB вихідного контракту:
| Рівень | Категорія безпеки NIST | Підписаний файл | Понад найменший |
|---|---|---|---|
| ML-DSA-44 | 2 | 138 202 байт | - |
| ML-DSA-65 | 3 | 140 650 байт | +2 448 байт |
| ML-DSA-87 | 5 | 143 971 байт | +5 769 байт |
Менше ніж 6 KB розділяє найслабший рівень від найсильнішого. Для контракту це нічого, що спрощує рішення: використовуйте ML‑DSA‑65 за замовчуванням, ML‑DSA‑87 там, де профіль вимагає категорію 5 або розмір не має значення, і ML‑DSA‑44 лише коли підписуєте стільки файлів, що кілобайти накопичуються у щось суттєве.
Крок 3 — Перевірка за допомогою публічного сертифікату
Отримувачу потрібен лише публічний сертифікат підписанта і нічого секретного:
with signature.Signature(signed_path) as sign:
options = DigitalVerifyOptions(certificate_path)
if password is not None:
options.password = password
return sign.verify(options).is_valid
У прикладі цей виклик здійснюється двічі над одним і тим же файлом: один раз з mldsa65.cer — публічною частиною ключа підпису, і один раз з іншим PFX підписанта. Перший повертає True, другий — False. Зауважте, що неправильний сертифікат дає False, а не викликає виключення — «підписано кимось іншим» це відповідь, яку ваш код має обробляти, а не помилку. Перевірка охоплює вміст документа разом з серійним номером та відбитком сертифікату, тому файл, змінений після підпису, також не проходить.
Крок 4 — Читання підписів з документа
Коли підписаний документ надходить, і ви не знаєте, який сертифікат очікувати:
with signature.Signature(signed_path) as sign:
found = sign.search(SignatureType.DIGITAL)
for item in found:
print(item.sign_time, item.is_valid)
search з SignatureType.DIGITAL повертає об’єкти DigitalSignature, що містять сертифікат, час підпису та прапорець валідності. Word‑документ може містити кілька підписів, включаючи мікс RSA та ML‑DSA, і кожен повідомляється зі своїм сертифікатом і власною валідністю.
Чи змінює це спосіб перевірки отримувачами?
Ні, жодних змін, які вони помітять, не відбувається. Отримувач і надалі потребує лише публічний сертифікат підписанта, передає його в той самий DigitalVerifyOptions і отримує булеве значення. Ніщо у шляху верифікації не специфічне для ML‑DSA. Єдине місце, де алгоритм проявляється, — індикатор підпису в Microsoft Word, який ще може не розпізнавати ML‑DSA, бо формат не має стандартного ідентифікатора.
Реальні сценарії використання
Довготривалі контракти
Найочевидніший випадок. Документ, який має залишатися верифікованим протягом десятиліть, підписується один раз, зараз, за допомогою ML‑DSA‑65 або ML‑DSA‑87, і більше не потребує перепідпису, бо алгоритм «вийшов» з експлуатації.
Регульовані середовища з визначеним профілем
Коли застосовується CNSA 2.0 або подібний профіль, рівень не є предметом дискусії — вимога ML‑DSA‑87, і єдине інженерне питання: чи підтримується формат.
Змішані конвеєри під час міграції
Підпис нових документів пост‑квантовим способом, залишаючи архів без змін, — цілком прийнятний проміжний стан, а search, що повідомляє про кожен підпис окремо, робить його керованим.
Кращі практики та поради
- Мігруйте за терміном зберігання, а не за обсягом. Документи, які потрібно зберігати довго, — це ті, що потребують змін; чек, важливий лише 90 днів, — ні.
- За замовчуванням використовуйте ML‑DSA‑65, якщо профіль не вказує інший рівень, і не турбуйтеся про різницю в розмірі — вона менша за 6 KB на підпис.
- Залишайте RSA, коли отримувач перевіряє в Word. Правильний підпис, який читач помітить як помилковий, гірший за повільну міграцію.
- Замініть тестові сертифікати. PFX‑файли у прикладі самопідписані з опублікованим паролем, тому будь‑яке підписання ними нічого не доводить.
- Перевіряйте після підпису у будь‑якому конвеєрі, використовуючи публічний сертифікат, який матиме отримувач.
Усунення типових проблем
Microsoft Word не показує підпис як дійсний. Очікувано на даний момент: немає стандартного ідентифікатора XML‑DSig для ML‑DSA, тому Word може його не розпізнати, хоча підпис правильний і GroupDocs.Signature його верифікує. Перевіряйте у власному конвеєрі і залишайте RSA для документів, чиї отримувачі покладаються на індикатор Word.
Виклик підписання відхиляє PDF або електронну таблицю. Підписання ML‑DSA охоплює формати Word — DOCX, DOC, ODT та інші. PDF, електронні таблиці та презентації ще не підтримуються, і їх треба підписувати RSA або ECDSA, як раніше.
Тема сертифікату повертається порожньою. Майже завжди це пастка dir() з Кроку 1: атрибут розв’язується динамічно, тому читайте його, а не перевіряйте наявність спочатку.
Висновок
Зміна коду — це зміна сертифікату, і саме це робить впровадження доцільним ще до того, як воно стане терміновим. Підписуйте довгоживучі Word‑документи ML‑DSA‑65, використовуйте ML‑DSA‑87 там, де профіль вимагає, перевіряйте за допомогою публічного сертифікату і залишайте RSA там, де формат або читач вимагає цього.
Запустіть приклад на одному зі своїх контрактів, і три розміри покажуть вам у байтах, скільки саме коштує найсильніший доступний рівень. У файлі, який я тестував, це було 5 769 байт.