مقال تقني

Ed448 و Brainpool ECDSA بلغة Pascal خالصة لملفات PDF

يوقّع PDFlibPas ويتحقق بواسطة Ed448 ومنحنيات Brainpool ECDSA الثلاثة بلغة Object Pascal خالصة. لا مكتبة تشفير خارجية، ولا مزوّد منصات، ولا ملف DLL: إذ ينفّذ PDFlibEd448 معيار RFC 8032 PureEdDSA على المنحنى edwards448، وينفّذ PDFlibBrainpool المنحنيات brainpoolP256r1 وbrainpoolP384r1 وbrainpoolP512r1 وفق RFC 5639. بُنيت الوحدتان بالطريقة نفسها، مقابل متجهات known-answer وُلِّدت بشكل مستقل قبل كتابة أي سطر بلغة Pascal، وكلتاهما تستحقان الكتابة عنها أساسا من أجل الأخطاء

حساب المجال هو شفرة صادقة بشكل استثنائي: فهي إما تطابق المتجهات المنشورة بايتا ببايت وإما لا تطابقها، فلا مجال لمنتج «يعمل في معظم الأحيان». ما يجعل الأمر صعبا هو أن التنفيذ الخاطئ يبقى منتِجا للتوقيعات، ويتحقق بنجاح من توقيعاته هو، ويبدو مقنعا تماما

لماذا هذه المنحنيات، ولماذا بلغة Pascal

تظهر منحنيات Brainpool في ملفات التوقيعات المؤهلة الأوروبية، لذا لا يمكن لمكتبة توقّع المستندات لهذا السوق أن تعتبرها منحنيات هامشية. أما Ed448 فمدرجة في مجموعة الخوارزميات التي يجلبها ISO/TS 32002 إلى PDF، حيث يكون الملخص الداخلي لها هو SHAKE256 بدلا من SHA-2. ولا تتوفر أي من العائلتين في مكتبات التشفير بلغة Pascal الشائعة الاستخدام، لذا على مكتبة PDF ترغب فيهما أن تمتلكهما بنفسها

وجهة النشر هي نفسها التي تنطبق على كل عمليات التشفير في هذه المكتبة: التطبيق الذي يُشحن كملف تنفيذي واحد دون أي اعتمادية تشفير لا يملك مزوّدا يُكتشف ولا إصدارا يُطابَق ولا سلوكا يتغير عند ترقيع النظام المضيف. والتوقيع هو بالضبط المجال الذي تقل رغبتك فيه في اعتمادية متحركة

الثوابت تأتي من نص المواصفة، لا من الذاكرة أبدا

كُتبت المحاولة الأولى للنقطة الأساس في edwards448 من الذاكرة وكانت خاطئة. هذا ليس خطأ لافتا لكنه خطأ مكلف جدا، لأن النقطة الأساس الخاطئة تنتج نظاما متسقا ذاتيا: توليد المفاتيح والتوقيع والتحقق لديك كلها تتفق مع بعضها وتخالف ما عداه من العالم

الإجراء العملي هو أخذ كل معامل من معاملات المجال من نص المواصفة ثم التحقق المتبادل. وفي edwards448 يعني ذلك الأخذ بالعدد الأولي وثابت المنحنى ورتبة المجموعة وكلا إحداثيي النقطة الأساس العشريين من RFC 8032، وتحويلها إلى التمثيل الداخلي بالأجزاء، ثم مطابقتها مع متجهات الاختبار المنشورة في الوثيقة نفسها. وفي منحنيات Brainpool يعني ذلك معاملات RFC 5639، وتنفيذا مستقلا كُتب لتوليد المتجهات، ومطابقة متبادلة مع مكتبة نظام في الاتجاهين قبل تشغيل أي كود Pascal

تتدفق معاملات المجال لكل من Ed448 وBrainpool من نص مواصفتي RFC 8032 وRFC 5639 إلى شكل الأجزاء ويجري التحقق المتبادل منها قبل تشغيل أي كود Pascal
تؤخذ معاملات المجال للمنحنى edwards448 ومنحنيات Brainpool من نص المواصفتين وتحوَّل إلى أجزاء ويجري التحقق المتبادل منها مقابل متجهات مستقلة

يستحق أحد اختصارات الاشتقاق تحذيرا لأنه يبدو عاما وهو ليس كذلك: استرجاع النقطة الأساس من قيمة y ثابتة يعمل مع منحنى 25519 ولا يعمل مع edwards448 حيث لا تملك تلك القيمة جذاذا تربيعيا. دحض سكربت ذلك في ثوانٍ، وهو أمر أرخص بكثير من اكتشافه عبر أداة التنقيح

الطريقة: تنفيذ مرآة على مستوى الأجزاء قبل أي كود Pascal

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

سير عمل تنفيذ مرآة بأعداد صحيحة غير محدودة للتحقق من حساب المجال وتوليد المفاتيح بلغة Pascal لكل من Ed448 وBrainpool
سير عمل المرآة من الأسفل إلى الأعلى: الحساب أولا، ثم توليد المفاتيح داخل المرآة، ثم كتابة كود Pascal ومقارنة القيم الوسطية

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

أربعة أسباب جذرية في Ed448

عُثر على الأربعة جميعها عبر التحقيق في القيم الوسطية، والأربعة جميعها من الصنف الذي ينتج مخرجات تبدو صحيحة

الأول هو فخ في الترميز. معظم الصيغ المنشورة لجمع Edwards الموحد تفترض ثابت منحنى يساوي سالب واحدا، في حين أن edwards448 ثابته زائد واحد. وعند نقلها دون تغيير، يُكتب بسط الإحداثي y كمجموع حيث يجب أن يكون فرقا. والحل ليس ترقيع الإشارة بل إعادة اشتقاق صيغة الضرب الخالية من الانعكاس من قانون الجمع الأفيني للمنحنى الصحيح، وهو ما ينتج أربع صيغ للإحداثيات ولا يترك مجالا لأن تورث إشارة من مصدر خاطئ

الثاني في فك ضغط النقطة. استرجاع الإحداثي الأفيني x من الإحداثيات الإسقاطية يتطلب ضربا واحدا بمقلوب Z. أما الضرب بمقلوب المربع فيعطي قيمة تظل تمثيلا إسقاطيا صالحا لكنها الإحداثي الأفيني الخاطئ، فيكون العَرَض إحداثي y صحيحا وإحداثي x خاطئا. ومتى كان أحد الإحداثيين صحيحا والآخر لا، فالخطأ في التطبيع لا في الحساب

الثالث عادة مستوردة من المنحنى الأقصر. يجب تخفيض العدد السلمي لكل توقيع والعدد السلمي للتحدي كلاهما من الملخص الكامل، وهو 114 بايت في Ed448، لا من أول 57 بايت منه. المنحنى ذو 32 بايت يستخدم ملخصه الكامل البالغ 64 بايت أيضا، فالقاعدة متسقة؛ الخطأ في الافتراض وحده القائل إن «نصف الملخص هو عرض العدد السلمي»

الرابع هو الترتيب. بادئة فصل المجال تأتي أولا، قبل بادئة السياق والرسالة، وهو ليس الترتيب الذي توحي به القراءة البديهية لكل من R وA في المواصفة. الخطأ هنا ينتج تواقيع تتحقق مقابل تنفيذك وحد لا غير، وهو أشد أنواع الفشل تضليلا

// تصميم حمل المجال: نشر بدلالات الأرضية (floor) خالصة، لذا تعمل
// الأجزاء الموجبة والسالبة معا والطرح لا يحتاج إلى إزاحة.
// يلتف الحمل العلوي عبر 2^448 = 2^224 + 1 (mod p)، فيشمل
// الجزء 0 والجزء 8. محدود بأربع جولات؛ لوحظ جولتان
// في الممارسة
procedure FeCarry(var A: TFe448);
var
  I, Round: Integer;
  Carry: Int64;
begin
  for Round := 1 to 4 do
  begin
    Carry := 0;
    for I := 0 to 15 do
    begin
      A[I] := A[I] + Carry;
      Carry := Floor28(A[I]);          // أرضية، لا قطع
      A[I] := A[I] - (Carry shl 28);
    end;
    if Carry = 0 then
      Break;
    A[0] := A[0] + Carry;              // 2^448 == 1
    A[8] := A[8] + Carry;              // 2^448 == 2^224
  end;
end;

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

سببان جذريان في Brainpool

الأول ليس تشفيرا أصلا. التمثيل العامل هو 33 جزءا، فحاصل ضرب قيمتين يحتاج 66، بينما صُرّحت مصفوفة الحاصل بـ 64. الكتابة بعد النهاية أفسدت الذاكرة المجاورة، وظهر ذلك أولا كنتائج خاطئة ولم يتحول إلى انهيار إلا بعد إضافة مسح أوسع. القاعدة المستخلصة تستحق التطبيق على كل مخزن رقمي ثابت الحجم: حدد الحجم من عرض الحاصل في أسوأ الحالات وأضف هامشا، ثم لا تفكر فيه مرة أخرى. المصفوفة في الكود المنشور هي 68 جزءا

الثاني شكل أُسّي مختلط. هناك شكلان صحيحان للتربيع والضرب يستهلكان الأس في اتجاهين متعاكسين: الشكل من اليمين إلى اليسار يضرب ثم يربع الأساس ويجب أن يقرأ البتات من الطرف الأدنى أهمية، بينما الشكل من اليسار إلى اليمين يربع ثم يضرب ويقرأ من الطرف الأعلى أهمية. حلقة الانعكاس المعياري كانت بجسم من اليمين إلى اليسار مع مسح بتات يبدأ بالأعلى أهمية. كلا النصفين مأخوذ من الكتب، أما الجمع بينهما فلا، والنتيجة مقلوب خاطئ يظل يبدو عنصر مجال معقولا

شكلان للتربيع والضرب باتجاهي بت متعاكسان والشكل المختلط الذي حسب مقلوبات معيارية خاطئة في Brainpool
كلا شكلي التربيع والضرب صحيح كل على حدة؛ ومزج جسم من اليمين إلى اليسار مع مسح يبدأ بالأعلى أهمية يعطي مقلوبا خاطئا معقولا المظهر
// مضاعفة وجمع في إحداثيات جاكوبي حيث قد يكون سجل الوجهة
// هو المتغير نفسه كمصدر. نسخ السجل كاملا عند الدخول هو
// الدفاع الموثوق الوحيد: كتابة أجزاء R تلوث قراءات P اللاحقة
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
  const Curve: TBPCurve);
var
  Pin: TBPPoint;
begin
  Pin := P;        // انسخ أولا، ثم احسب من Pin وحده
  // ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;

درسان في العمليات كلفتا أكثر من الأخطاء

الترقيع السريع المتزايد لا يقارب نتيجة في وحدة تشفير. رُقّعت مسودة واحدة مرارا حتى حملت 32 روتينا مكررا وبنية متضررة، ولم تُصلح إلا بإعادة كتابتها. النمط الواجب تبنّيه إما كتابتها مرة واحدة من مرآة متحقق من صحتها أو إعادة كتابتها؛ فسلسلة الإصلاحات المحلية لحساب لا تفهمه بعد تتراكم أسرع مما تصحح

وتحقق من الطابع الزمني للملف التنفيذي قبل أن تصدق نتيجة اختبار. البناء المتزايد الذي يترجم دون إعادة ربط يشغل الملف التنفيذي السابق، وهو ما صنع جولة كاملة من الخيوط الزائفة عن تحقيقات مفقودة ومخرجات مكررة. عند تنقيح التشفير، ينبغي أن تستدعي النتيجة غير المفسرة سؤال «هل هذا الملف التنفيذي الذي بنيته للتو» قبل سؤال «هل الخوارزمية خاطئة»

الأداء والنطاق وطريقة الاستدعاء

التخفيض المعياري في وحدة Brainpool هو طرح إزاحي متسلسل بالبت من أعلى بت مضبوط في الحاصل، فتكلفة الضرب تقع في حدود عرض البت تقريبا. التحقق بمنحنى P-256 يستغرق بضع مئات من المللي ثانية في حدها الأدنى، وهو أمر عادي لتوقيع المستندات أو التحقق منها لكنه غير كافٍ لمنهي TLS. التخفيض بطريقة Barrett هو الترقية البديهية ويحتاج قيمة عمل أوسع مما يحمله التمثيل الحالي، فهو تغيير يجري عند طلبه بحمل عمل لا استباقيا

uses
  PDFlibEd448, PDFlibBrainpool;

var
  PublicKey, Signature: AnsiString;
  Curve: TBPCurve;
  R, S, PubX, PubY: TBPValue;
begin
  // Ed448: PureEdDSA، وSHAKE256 داخليا، ومفاتيح بحجم 57 بايت
  if Ed448PublicKeyFromSeed(Seed, PublicKey) and
     Ed448Sign(DocumentDigest, Seed, Signature) then
    Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));

  // Brainpool: المتصل يزود العدد العشوائي (nonce) لكل توقيع،
  // فتبقى سياسة nonce مع التطبيق
  Curve := BPLoadCurve(bpP256r1);
  if BPKeyGen(PubX, PubY, PrivateD, Curve) and
     BPSignFixedK(R, S, Hash, PrivateD, Nonce, Curve) then
    Assert(BPVerify(R, S, Hash, PubX, PubY, Curve));
end;

لاحظ أن نقطة دخول التوقيع في Brainpool تأخذ العدد العشوائي (nonce) بدل توليده. هذا مقصود: فتوليد nonce هو أكثر ما يمكن إفساده كارثيا في ECDSA، إذ تكشف القيمة المكررة أو المتوقعة المفتاح الخاص، وقرار مصدر العشوائية يعود إلى التطبيق ونظامه الامتثالي، لا إلى مكتبة PDF

تقع هذه المنحنيات إلى جانب عمل ما بعد الكم الموصوف في مقال FIPS 204 ML-DSA، وتتصل بمسار التوقيع والتحقق نفسه المشمول في توقيع PAdES والتحقق منه. أما شهادات الاختبار على هذه المنحنيات فمسار التوليد المحلي موصوف في الشهادات الموقعة ذاتيا بواسطة CryptoAPI. مصفوفة الخوارزميات الكاملة مدرجة في صفحة منتج losLab PDF Developer Library