💡 مثال كامل يعمل متاح على 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‑bit — الحزمة تُرفق مع وقت تشغيل .NET مدمج ولا توجد عجلة 32‑bit.
  • 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 بايت.

موارد إضافية