💡 مثال کامل قابل اجرا در گیت‌هاب موجود است:
نمونه کامل کار با گواهی‌نامه‌های ML‑DSA برای امضای DOCX با پایتون

مقدمه

یک قرارداد را این بعدازظهر با RSA‑2048 امضا کنید و قولی داده‌اید که تا زمانی که قرارداد مهم است پابرجا بماند. اگر این مدت بیست یا سی سال باشد — که برای اسناد، فرم‌های رضایت و تأیید مهندسی معمول است — این وعده باید از الگوریتم پیشی بگیرد. حمله نیازی به وجود داشتن امروز ندارد؛ فقط باید پیش از این که سند دیگر مهم شود وجود داشته باشد، تا هر کسی که کلید عمومی را دارد بتواند کلید خصوصی را استخراج کرده و به نام شما امضا کند.

امضای سند پساکوانتومی ویژگی GroupDocs.Signature برای پایتون است که این وعده را با الگوریتم ML‑DSA که در سال 2024 به عنوان FIPS 204 توسط NIST استاندارد شد، جایگزین می‌کند. پشتیبانی از فرمت Word در GroupDocs.Signature نسخه 26.9 اضافه شد و API که قبلاً داشتید را دوباره استفاده می‌کند: یک کلید ML‑DSA در یک فایل PFX قرار می‌گیرد و دقیقاً همانند کلید RSA به DigitalSignOptions داده می‌شود.

این راهنما یک فایل DOCX را در چهار گام امضا می‌کند، سه سطح امنیتی را بر روی خروجی اندازه‌گیری‌شده مقایسه می‌کند، امضا را فقط با یک گواهی‌نامه عمومی تأیید می‌کند و در پایان دو محدودیتی را که پیش از تعهد باید بدانید، بیان می‌کند.

چرا این موضوع مهم‌تر از مهاجرت معمول است

مهاجرت امضا برخلاف مهاجرت رمزنگاری در یک جنبه متفاوت است که باعث می‌شود به تعویق انداختن آن آسان‌تر و رفع آن دشوارتر باشد.

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

به همین دلیل توصیه عملی محدود به موارد خاص است نه همه‌جانبه: اسنادی که نگهداری طولانی‌مدت دارند را مهاجرت کنید، بقیه را همان‌جا بگذارید. برخی پروفایل‌ها پیشاپیش معیار را تعیین کرده‌اند — CNSA 2.0 برای سامانه‌های امنیت ملی ML‑DSA‑87 را می‌طلبد — و برای بقیه عامل تصمیم‌گیری این است که فایل تا چه مدت باید قابل دفاع بماند.

پیش‌نیازها

  • پایتون 3.9 یا بالاتر بر روی مفسری 64‑بیتی — بسته یک زمان اجرا .NET بسته‌بندی‌شده می‌آورد و چرخ‌دنده 32‑بیتی ندارد
  • GroupDocs.Signature برای پایتون از طریق .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 متفاوت اشاره دهید. گزینه جدیدی نیست، پارامتر الگوریتم جداگانه‌ای نیست، شاخه‌ای برای پساکوانتوم وجود ندارد.

خواندن امضاکننده پس از امضا یک گام دیگر می‌گیرد و تله خاص پایتونی زیر را شامل می‌شود:

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() بررسی کردم، نتیجه‌گیری کردم که موضوع (subject) در دسترس نیست و اشتباه بودم — بنابراین اگر قبل از خواندن introspect کنید، مقدار موجود را از دست می‌دهید.

گام 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 را استفاده کنید، در جایی که پروفایل نیاز به دسته 5 دارد یا اندازه مهم نیست ML‑DSA‑87، و فقط زمانی که تعداد فایل‌های امضا شده به‌قدری زیاد است که کیلوبایت‌ها به مقدار واقعی تبدیل می‌شوند از 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 اعتبارسنجی می‌کند نگه دارید. امضاهای صحیحی که خوانندهٔ Word علامت‌گذاری می‌کند، از مهاجرت کندتر بهتر است.
  • گواهی‌نامه‌های آزمایشی را جایگزین کنید. فایل‌های PFX نمونه خودامضا هستند با رمز عبور منتشرشده، بنابراین هر امضایی که با آن‌ها انجام شود، هیچ‌چیزی ثابت نمی‌کند.
  • پس از امضا تأیید کنید در هر خط لوله‌ای، با استفاده از گواهی‌نامه عمومی که گیرنده دارد.

عیب‌یابی مشکلات رایج

Microsoft Word امضا را به‌عنوان معتبر نشان نمی‌دهد. در حال حاضر این انتظار می‌رود: هنوز شناسهٔ استاندارد XML‑DSig برای ML‑DSA وجود ندارد، بنابراین Word ممکن است آن را تشخیص ندهد حتی اگر امضا صحیح باشد و GroupDocs.Signature آن را تأیید کند. در خط لولهٔ خود تأیید کنید و برای اسنادی که گیرندگان به نشانگر Word وابسته‌اند، RSA را نگه دارید.

فراخوانی امضا PDF یا صفحه‌گسترده‌ای را رد می‌کند. امضای ML‑DSA فقط فرمت‌های Word — DOCX، DOC، ODT و غیره — را پوشش می‌دهد. PDF، صفحه‌گسترده‌ها و ارائه‌ها هنوز پشتیبانی نمی‌شوند و باید همچنان با RSA یا ECDSA امضا شوند.

موضوع گواهی‌نامه خالی برمی‌گردد. تقریباً همیشه به دلیل تلهٔ dir() در گام 1 است: ویژگی به‌صورت پویا حل می‌شود، بنابراین آن را بخوانید نه اینکه ابتدا وجود آن را تست کنید.

نتیجه‌گیری

تغییر کد فقط تغییر گواهی‌نامه است، که همان بخشی است که این کار را پیش از اضطراری شدن ارزشمند می‌کند. اسناد Word طولانی‌عمر را با ML‑DSA‑65 امضا کنید، در جایی که پروفایل نیاز دارد از ML‑DSA‑87 استفاده کنید، با گواهی‌نامه عمومی تأیید کنید و RSA را در جایی که فرمت یا خواننده نیاز دارد، نگه دارید.

نمونه را روی یکی از قراردادهای خود اجرا کنید و سه اندازه به بایت دقیقاً نشان می‌دهند که قوی‌ترین سطح موجود چه هزینه‌ای دارد. در فایلی که من تست کردم، 5,769 بایت بود.

منابع تکمیلی