تخطّ إلى المحتوى
Z
← العودة للمدونة

نُشر في · 31 أغسطس 2026 · 2 د قراءة

الختم التشفيري ECDSA لزاتكا: شرح بلغة بسيطة

كيف يتناسب توقيع ECDSA وتجزئة الفاتورة السابقة وعداد UUID و C14N معاً في فاتورة زاتكا المرحلة الثانية — وما يجب مراجعته في أي حل من مزود.

صورة غلاف شرح الختم التشفيري ECDSA لزاتكا يظهر مفتاحاً وسلسلة تجزئات فواتير وختم توقيع رقمي أخضر

كل فاتورة زاتكا مرحلة ثانية تحمل ختم تشفيري يمكن لزاتكا التحقق منه — حتى بعد سنوات من الإصدار — دون الحاجة إلى الاتصال بالخادم. الختم هو توقيع ECDSA على XML الفاتورة المعياري، المنتج بمفتاح خاص مرتبط بشهادة CSID الخاصة بك. هذه الصفحة هي الشرح بلغة بسيطة لكيفية تناسب القطع معاً، ولماذا توجد كل قطعة، وما يجب التحقق منه في أي حل تقيمه. للصورة الكبيرة، راجع دليل المرحلة الثانية الكامل؛ للتخطيط على مستوى البايت لـ XML، راجع شرح مخطط UBL 2.1؛ لتدفق التسجيل الذي ينتج المفتاح، راجع

ECDSA هي اختصار لـ خوارزمية التوقيع الرقمي بالمنحنى الإهليلجي. هي مخطط توقيع بمفتاح عام، من نفس عائلة RSA، لكنها مبنية على مسألة رياضية صعبة مختلفة. بينما تعتمد RSA على صعوبة تحليل عدد صحيح كبير جداً، تعتمد ECDSA على صعوبة مسألة اللوغاريتم المتقطع للمنحنى الإهليلجي. النتيجة العملية هي أن ECDSA تمنحك نفس الأمان مثل RSA-2048 بمفتاح أقصر بكثير — عادة 256 بت — وتنتج توقيعات أقصر بكثير. هذا مهم لرموز QR وأحجام تضمين PDF، وكلاهما تقيدهما زاتكا.

تتطلب زاتكا على وجه التحديد ECDSA على منحنى NIST P-256 (يعرف أيضاً بـ secp256r1 أو prime256v1). التوقيع عددان صحيحان، r و s، كل واحد 256 بت، مشفر بـ base64. الحمولة الموقعة الإجمالية تقريباً 88 بايت. للمقارنة، توقيع RSA-2048 مكافئ هو 256 بايت. ECDSA أيضاً يوقع أسرع ويتحقق أسرع — مهم عندما تعالج آلاف الفواتير يومياً.

الأربعة أجزاء المتحركة داخل ختم زاتكا المرحلة الثانية

هناك ميل للحديث عن "التوقيع" كما لو كان شيئاً واحداً. في الواقع، الختم التشفيري على فاتورة زاتكا المرحلة الثانية هو تفاعل منسق بين أربعة حقول متميزة:

  1. CSID (معرف حل الشهادة) — JWT تصدره زاتكا يحتوي على مفتاحك العام ورقمك الضريبي ومعرف الجهاز. يتم توليده كجزء من تدفق W3C XML-Signature يحكم تنسيق التضمين.

إذا كان أي من هذه الأربعة مفقوداً أو خاطئاً أو قديماً، ترفض فاتورة الفاتورة. أدناه تدفق البيانات الذي ينتج الأربعة جميعها.

الخطوة 1: ابنِ فاتورة UBL XML

هذا هو مستند UBL 2.1 XML الذي بنيته بالفعل في المجموعة الثانية. الشيء الوحيد الذي يجب معرفته للتشفير هو أن XML النهائي يجب أن يكون معيارياً قبل التوقيع — أي أن البايتات التي تجزئها وتوقعها يجب أن تكون الشكل المعياري، وليس الشكل المطبوع بشكل جميل. سنعود إلى التوحيد القياسي في الخطوة 4.

الخطوة 2: احسب تجزئة الفاتورة السابقة (PIH)

اقرأ آخر فاتورة ناجحة أصدرها نظامك، خذ تجزئة SHA-256 لبايتات XML المعيارية الخاصة بها، شفرها بـ base64، وضعها في حقل <cbc:PrecedingInvoiceHash>. لأول فاتورة في سلسلة جهاز، اترك الحقل فارغاً (أو اضبطه على base64 لـ 32 بايت صفر، حسب إصدار مواصفات زاتكا الذي تستهدفه).

كود زائف:

prev_hash_b64 = ""\nif last_successful_invoice is not None:\n    canonical_bytes = c14n(last_successful_invoice.xml)\n    prev_hash_b64 = base64(sha256(canonical_bytes))

السلسلة تعمل فقط إذا وقعت كل فاتورة البايتات المعيارية لسابقتها، وكل فاتورة تضم التجزئة الناتجة. العبث بفاتورة #5 ولن تتطابق التجزئة المخزنة في #6 — وتكتشف زاتكا الكسر في الاستدعاء التالي مباشرة.

الخطوة 3: ولّد العداد / UUID

تتطلب زاتكا أن يكون حقل العداد UUID v4 (موصى به) أو عدداً صحيحاً متسلسلاً. الحقل هو <cbc:UUID> في مستند UBL، ويجب أن يكون فريداً لكل جهاز طوال عمر الجهاز. UUID v4 يمنحك 122 بت من العشوائية — خطر التصادم قريب من الصفر فعلياً. عداد صحيح يمنحك البساطة، لكنه ينكسر إذا ألغيت رقماً يوماً.

كود زائف:

new_uuid = uuid4()  # مثل "9d7e8f1a-3b2c-4e5d-8a9b-1c2d3e4f5a6b"\ninvoice.uuid = new_uuid\nledger.append(invoice.uuid)

خزّن العداد في قاعدة بيانات تدعم معاملات ACID. إذا فقد مخزن العداد لديك كتابة، فإن استدعاءك التالي يفشل. معظم الفرق تستخدم جدول zatca_counters منفصل مع قيد فريد على معرف الجهاز + UUID، بحيث يتم التقاط UUIDs المكررة في وقت الإدراج.

الخطوة 4: وحّد، جزئ، ووقّع

هذا هو الجزء الذي يخطئ فيه معظم المطورين في المحاولة الأولى. يجب عليك:

  1. خذ XML الفاتورة، بما في ذلك كتلة <ds:Signature> النائب مع <ds:SignatureValue> فارغ.
  2. طبق التوحيد القياسي الحصري لـ XML (C14N 1.0) وفقاً لمواصفات W3C. التوحيد القياسي الافتراضي الذي تقبله زاتكا هو C14N مع إزالة التعليقات. بعض التطبيقات تتطلب أيضاً C14N 1.1 ("حصري") لتجنب مشاكل بادئة مساحة الاسم.
  3. احسب تجزئة SHA-256 للبايتات المعيارية.
  4. وقّع التجزئة بمفتاحك الخاص باستخدام ECDSA P-256 + SHA-256. الإخراج عددان صحيحان r و s، كل واحد 32 بايت، متسلسلان ومشفّران بـ base64.
  5. أدرج التوقيع المشفر بـ base64 في عنصر <ds:SignatureValue>.

تدفق التوقيع في كود زائف:

canonical_xml = c14n(invoice_xml)            # C14N 1.0، بدون تعليقات\ndigest = sha256(canonical_xml)\nraw_sig = ecdsa_sign(private_key, digest)   # 64 بايت: r || s\nsig_b64 = base64(raw_sig)\ninvoice.signature_value = sig_b64

مزلتان شائعتان. أولاً، لا توقّع XML المطبوع بشكل جميل؛ وقّع البايتات المعيارية فقط. المكتبات المختلفة تنتج مسافات بيضاء ونهايات أسطر وترتيب مساحات أسماء مختلفة، وأي منها سينتج توقيعاً مختلفاً. ثانياً، لا توقّع تجزئة العنصر النائب للتوقيع؛ وقّع البايتات المعيارية للفاتورة بأكملها، بما في ذلك العنصر النائب للتوقيع.

الخطوة 5: أرسل إلى فاتورة

أرسل XML الموقّع عبر POST إلى /e-invoicing/clearance/single (B2B) أو /e-invoicing/reporting/single (B2C). أدرج CSID في رأس Authentication كرمز Bearer. تتحقق فاتورة من التوقيع والسلسلة والعداد والمخطط. إذا تطابق كل شيء، ترجع تجزئة تخليص (B2B) أو إيصال إبلاغ (B2C). تختم تلك التجزئة على PDF المطبوع.

كود زائف:

response = http.post(\n    "https://fatoorah.zatca.gov.sa/e-invoicing/clearance/single",\n    headers={\n        "Authorization": f"Bearer {csid_jwt}",\n        "Content-Type": "application/xml",\n    },\n    body=signed_xml,\n)\nclearance_hash = response.json()["clearanceHash"]\ncounter = response.json()["counter"]\npdf.stamp_with(clearance_hash, counter)

تجزئة التخليص نفسها هي تجزئة SHA-256 تحسبها زاتكا على XML الموقّع الخاص بك. هي ما ستشير إليه الفاتورة التالية في سلسلتك عبر PrecedingInvoiceHash. عالج دفتر الأستاذ (تجزئة_التخليص، العداد، XML_الموقّع) كمصدرك الموثوق — إذا دققت زاتكا يوماً، فهذا ما تطلبه.

ما يجب مراجعته في حل مزود

إذا كنت تقيم مزود مرحلة ثانية لزاتكا بدلاً من بناء التكامل بنفسك، فإن الصحة التشفيرية هي أهم شيء يجب التحقق منه. المزود هو وصي مفتاحك الخاص ومنتج كل توقيع. إذا كان تشفيرهم مهملاً، فواتيرك غير متوافقة حتى لو بدا كل شيء آخر على ما يرام.

  • أين يُخزّن المفتاح الخاص؟ على القرص، مشفراً في حالة السكون؟ في وحدة أمان الأجهزة (HSM)؟ في KMS سحابي مثل AWS KMS أو Azure Key Vault أو Google Cloud KMS؟ خيار القرص هو الأرخص ولكن الأكثر خطورة. خيار HSM / KMS السحابي هو الأكثر أماناً.
  • ما التوحيد القياسي الذي يستخدمه المزود؟ اسأل عن المكتبة والإصدار الدقيق. C14N 1.0 مع إزالة التعليقات هو الحد الأدنى. C14N 1.1 (حصري) أكثر أماناً. إذا لم يستطع المزود تسمية مكتبته، امشِ بعيداً.
  • كيف يتم توليد العداد؟ UUID v4 من CSPRNG، أو تسلسل قاعدة بيانات؟ UUIDs أكثر أماناً. التسلسلات تنكسر إذا فقدت قاعدة البيانات كتابة.
  • هل يوقّع المزود البايتات المعيارية أم البايتات المطبوعة بشكل جميل؟ إذا كان الجواب "لا نعرف"، فهذا هو الجواب. ابحث عن مزود يمكنه الإجابة على السؤال.
  • هل يحتفظ المزود بتجزئة التخليص والعداد؟ ستحتاجهما للفاتورة التالية في السلسلة. إذا لم يدمجهما المزود، تنكسر السلسلة.
  • ماذا يحدث عند استدعاء فاشل؟ هل يعيد المزود المحاولة، وهل يعيدها بنفس UUID؟ المحاولات بـ UUID جديد تكسر السلسلة.

أخطاء التشفير الشائعة في الإنتاج

بعد 4 سنوات من عمليات نشر المرحلة الثانية في السعودية، مجموعة صغيرة من أخطاء التشفير تمثل غالبية إخفاقات التكامل:

1. توقيع XML المطبوع بشكل جميل. الخطأ الأكثر شيوعاً. الإصلاح هو دائماً التوحيد القياسي قبل التوقيع، وتوقيع البايتات المعيارية فقط. معظم اللغات لديها مكتبة توحيد قياسي: lxml في Python، Apache Santuario في Java، xml-c14n في Node.

2. تجزئة خاطئة للفاتورة السابقة. يجب أن تكون تجزئة السلسلة هي SHA-256 للفاتورة السابقة المعيارية، وليس البايتات المطبوعة بشكل جميل، وليس تجزئة التخليص التي أرجعتها زاتكا، وليس XML الفاتورة المخزّن في ERP الخاص بك. تجزئة السلسلة التي تخزنها على جانبك هي ما توقعه في الاستدعاء التالي. احصل عليها من نفس مسار الكود الذي يوقّع.

3. انتهاء صلاحية CSID في الإنتاج. تنتهي صلاحية CSIDs بعد سنة واحدة (إنتاج) أو 90 يوماً (امتثال). إذا نسيت التدوير، فإن كل توقيع تنتجه يكون غير صالح. ضع تذكيراً في التقويم قبل 60 يوماً من انتهاء الصلاحية.

4. UUIDs معاد استخدامها عبر الأجهزة. CSID مرتبط بجهاز. إذا شاركت مفتاحاً خاصاً عبر جهازين وأصدرت فواتير بنطاقات UUID متداخلة، تنكسر السلسلة عند حدود الجهاز. CSID واحد لكل جهاز. دائماً.

5. تخطي السلسلة بعد POST فاشل. إذا فشل POST الخاص بك إلى فاتورة بعد أن تكون قد وقّعت بالفعل، لا تعد المحاولة بـ UUID جديد. أعد محاولة نفس الفاتورة الموقّعة بنفس UUID. إذا كان عليك توليد UUID جديد لأن السابق عالق بشكل دائم، يجب أن تضع علامة على الفجوة صراحة في دفتر أستاذك حتى يتمكن المدقق من رؤية الفجوة.

ما الذي تتحقق منه زاتكا فعلياً عند التحقق

عندما تتلقى فاتورة الفاتورة الخاصة بك، فإنها تجري خمسة فحوصات بهذا الترتيب:

  1. التحقق من المخطط. هل XML صالح UBL 2.1 + امتداد زاتكا؟ أخطاء المخطط ترفض قبل تشغيل أي تشفير.
  2. تفرد العداد. هل شوهد هذا UUID من قبل؟ إذا نعم، ارفض.
  3. فحص تجزئة السلسلة. هل حقل PrecedingInvoiceHash يطابق SHA-256 للفاتورة السابقة في سلسلتك؟ إذا لا، ارفض.
  4. التحقق من توقيع ECDSA. هل SignatureValue يتحقق ضد كتلة SignedInfo، باستخدام المفتاح العام في CSID؟ إذا لا، ارفض.
  5. تكافؤ التوحيد القياسي. هل الشكل المعياري للفاتورة يطابق الشكل المعياري الذي حسب عليه التوقيع؟ إذا لا، ارفض.

الخطوات من 2 إلى 5 هي ما يجعل الختم التشفيري مفيداً. ممثل خبيث يعبث بـ XML فاتورة لا يمكنه إنتاج توقيع صالح لأنه لا يملك المفتاح الخاص. ممثل خبيث يعبث بـ XML الفاتورة السابقة لا يمكنه إبقاء السلسلة صالحة لأن تجزئة SHA-256 لن تتطابق. التركيبة هي ما يمنح النظام قوة كشف الاحتيال — ولهذا السبب فإن سرقة مفتاح خاص هي حدث "عار ولوم" يستحق المعالجة بجدية اختراق قاعدة بيانات الإنتاج.

الأسئلة الشائعة

هل يمكنني استخدام RSA-2048 بدلاً من ECDSA؟
تقبل زاتكا كليهما، لكن ECDSA P-256 هو الافتراضي الموصى به في إصدارات المواصفات 2024+. ينتج RSA-2048 توقيعات أكبر وتحقق أبطأ، وsandbox فاتورة أكثر صرامة حول أوضاع حشو RSA من ECDSA. إذا كنت تبدأ من الصفر، استخدم ECDSA. إذا كان لديك تكامل RSA-2048 قائم ويعمل، اتركه؛ لن تكسره زاتكا.

ماذا يحدث إذا تعطلت منصة فاتورة للصيانة؟
لا يمكنك إصدار فواتير B2B أثناء انقطاع فاتورة. فواتير B2C (الإبلاغ) لا يزال بإمكانك إصدارها محلياً، لكن يجب عليك الإبلاغ عنها بأثر رجعي عندما تعود فاتورة — ولا يزال يجب أن تدرج توقيعاً صالحاً، حتى لو تأخر POST الإبلاغ. التوقيع لا يعتمد على اتصال فاتورة.

هل يمكن للمشتري التحقق من التوقيع على فاتورة استلمها؟
نعم، إذا كان لدى المشتري نسخة من CSID البائع. يفك المشتري تشفير المفتاح العام، يحسب SHA-256 على XML المعياري، ويتحقق من توقيع ECDSA. تنشر زاتكا أداة تحقق لهذا، وتتضمن بوابة فاتورة صفحة "تحقق من فاتورة" يمكن لأي شخص استخدامها.

هل أحتاج مفتاحاً مختلفاً للإنتاج وsandbox؟
نعم. CSID الامتثال و CSID الإنتاج يصدران من تسلسلات شهادات مختلفة. مفتاح خاص يوقع في sandbox سيرفض في الإنتاج، والعكس. أبقهما منفصلين تماماً، أو تخاطر بأن نقطة نهاية فاتورة الإنتاج ترفض الفواتير لأن سلسلة الشهادات لا تتطابق.

كم من الوقت يبقى التوقيع صالحاً؟
التوقيع نفسه لا تنتهي صلاحيته، لكن CSID تنتهي. بمجرد انتهاء صلاحية CSID الخاص بك، لا تزال التوقيعات القديمة تتحقق، لكن لا يمكنك إصدار فواتير جديدة حتى تدور. سجل التدقيق سليم؛ فقط الإصدار النشط محظور. هذا هو التصميم الصحيح — يعني أن التوقيع قابل للتحقق إلى الأبد، لكن مفتاح التوقيع النشط يتم تدويره على التقويم.

هل توجد طريقة لجعل التكامل أبسط؟
نعم — حل SaaS معتمد من زاتكا يتعامل مع كل هذا نيابة عنك. المقايضة هي التكلفة والاعتماد على المزود. إذا كنت تعالج أقل من ~5000 فاتورة شهرياً، فإن مسار SaaS هو الصحيح. لحجم أعلى، تكلفة التكامل المباشر تستهلك خلال 6-12 شهراً. يغطي دليل الموجات تحليل التكلفة بمزيد من التفصيل.

عرض الكل →
عرض جميع المقالات →

المصادر والمراجع

المصادر الرسمية المعتمدة في هذا الدليل:

احصل على الدليل القادم للمال في السعودية

رسالة بريد إلكتروني قصيرة شهرياً بدليل سعودي جديد للمال أو تحديث حاسبة أو نصيحة زاتكا. بلا مزعج، يمكنك إلغاء الاشتراك في أي وقت.

أوافق على تلقي رسائل تسويقية من هذا الموقع. يمكنك إلغاء الاشتراك في أي وقت.

أنشئ فاتورة زاتكا مع رمز QR الآن

أضف الرقم الضريبي المكون من 15 رقماً والمبالغ، ثم حمّل PDF متوافقاً مع المرحلة الأولى — مجاناً وأونلاين بدون تسجيل.

افتح المولّد