مقاله فنی

Ed448 و Brainpool ECDSA در پاسکال خالص برای PDF

PDFlibPas با Ed448 و با سه منحنی Brainpool ECDSA در Object Pascal خالص امضا و تأیید می‌کند. هیچ کتابخانه رمزنگاری خارجی، هیچ ارائه‌دهنده پلتفرمی، هیچ DLL‌ای در کار نیست: PDFlibEd448 نگارش PureEdDSA از RFC 8032 را روی edwards448 پیاده می‌کند و PDFlibBrainpool منحنی‌های brainpoolP256r1، brainpoolP384r1 و brainpoolP512r1 را از RFC 5639. هر دو به یک شکل ساخته شده‌اند: در برابر بردارهای known-answer که پیش از نوشتن حتی یک خط پاسکال به‌طور مستقل تولید شده بودند، و ارزش نوشتن درباره هر دو هم بیشتر به‌خاطر باگ‌هایشان است

حساب میدانی کدی به‌طرزی غیرمعمول صادق است. یا بایت‌به‌بایت با بردارهای منتشرشده مطابقت دارد یا ندارد، پس جای «تقریباً کار می‌کند» وجود ندارد. چیزی که کار را دشوار می‌کند این است که یک پیاده‌سازی غلط همچنان امضا تولید می‌کند، همچنان امضاهای خودش را تأیید می‌کند، و همچنان کاملاً موجه به‌نظر می‌رسد

چرا این منحنی‌ها، و چرا در پاسکال

منحنی‌های Brainpool در پروفایل‌های امضای موصوف اروپایی ظاهر می‌شوند، پس کتابخانه‌ای که برای آن بازار سند امضا می‌کند نمی‌تواند آن‌ها را غریبه بداند. Ed448 در مجموعه الگوریتمی قرار دارد که ISO/TS 32002 به PDF می‌آورد، جایی که digest داخلی آن SHAKE256 است نه SHA-2. هیچ‌کدام از این دو خانواده در کتابخانه‌های رمزنگاری پاسکال رایج در دسترس نیستند، پس کتابخانه PDF ای که این‌ها را می‌خواهد باید مالکشان باشد

استدلال استقرار همان استدلالی است که بر همه رمزنگاری این کتابخانه حاکم است: برنامه‌ای که یک باینری بدون هیچ وابستگی رمزنگاری عرضه می‌کند، نه ارائه‌دهنده‌ای برای شناسایی دارد، نه نسخه‌ای برای تطبیق، و نه رفتاری که وقتی میزبان وصله می‌شود تغییر کند. امضا دقیقاً همان حوزه‌ای است که کمترین میل به یک وابستگی متغیر را دارید

ثابت‌ها از متن مشخصات می‌آیند، هرگز از حافظه

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

رویه کاری این است که هر پارامتر دامنه را از متن مشخصات بردارید و سپس به‌طور متقاطع راستی‌آزمایی کنید. برای edwards448 یعنی عدد اول، ثابت منحنی، مرتبه گروه و هر دو مختصات اعشاری نقطه پایه از RFC 8032، تبدیل‌شده به نمایش داخلی limb، و سپس کنترل‌شده در برابر بردارهای آزمون منتشرشده همان سند. برای منحنی‌های Brainpool یعنی پارامترها از RFC 5639، یک پیاده‌سازی مستقل که برای تولید بردارها نوشته شد، و یک کنترل متقاطع در برابر یک کتابخانه سیستمی در هر دو جهت، پیش از آنکه حتی یک خط پاسکال اجرا شود

پارامترهای دامنه Ed448 و Brainpool از متن مشخصات RFC 8032 و RFC 5639 به فرم limb جریان می‌یابند و پیش از اجرای هر پاسکالی به‌طور متقاطع راستی‌آزمایی می‌شوند
پارامترهای دامنه edwards448 و منحنی‌های Brainpool از متن RFC برداشته، به limb تبدیل و در برابر بردارهای مستقل کنترل متقاطع می‌شوند

یک میان‌بر استخراج شایسته هشدار است چون جهانی به‌نظر می‌رسد و نیست: بازیابی نقطه پایه از یک مقدار y ثابت برای منحنی 25519 کار می‌کند و برای edwards448 کار نمی‌کند، جایی که آن مقدار ریشه دوم ندارد. یک اسکریپت آن را در چند ثانیه رد کرد، که بسیار ارزان‌تر از کشف‌کردنش با دیباگر است

روش: یک آینه در تراز limb پیش از هر پاسکالی

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

گردش کار پیاده‌سازی آینه‌ای با اعداد صحیح بدون کران که حساب میدانی پاسکال و تولید کلید را برای Ed448 و Brainpool راستی‌آزمایی می‌کند
گردش کار آینه‌ای از پایین به بالا: نخست حساب، سپس تولید کلید درون آینه، بعد پیاده‌سازی پاسکال و مقایسه مقادیر میانی

سود آن تشخیصی است نه توسعه‌ای. وقتی مشخص شود آینه درست است، هر ناهماهنگی میان آینه و پاسکال یک لغزش پیاده‌سازی است، و کاویدن همان مقدار میانی در هر دو پیاده‌سازی فوراً محل آن را پیدا می‌کند. این کار یک رده باگ که در غیر این صورت تقریباً غیرقابل دیباگ است، یک limb غلط واحد در اعماق یک ضرب اسکالر، به یک مقایسه پنج‌دقیقه‌ای تبدیل می‌کند

چهار علت ریشه‌ای در Ed448

هر چهار مورد با کاویدن مقادیر میانی پیدا شدند، و هر چهار مورد از آن جنس‌اند که خروجی موجه‌نما تولید می‌کنند

نخستین یک تله نمادگذاری است. بیشتر فرمول‌های منتشرشده برای جمع یکپارچه ادواردز ثابت منحنی منفی یک را فرض می‌کنند، و edwards448 مثبت یک دارد. با انتقال بدون تغییر، صورت کسر مختصات y به‌صورت جمع نوشته می‌شود در حالی که باید تفریق باشد. راه‌حل وصله‌زدن به علامت نیست بلکه بازاستخراج فرم ضرب بدون وارون‌سازی از قانون جمع آفین برای منحنی درست است، که چهار عبارت مختصات را تولید می‌کند و جایی برای موروثت علامت از منبع غلط باقی نمی‌گذارد

دومی در فشرده‌گشایی نقطه است. بازیابی x آفین از مختصات تصویری نیازمند یک ضرب در وارون Z است. ضرب در مجذور وارون مقداری می‌دهد که همچنان یک نمایش تصویری معتبر است و مختصات آفین غلط است، پس نشانه آن y درست همراه x غلط است. هر وقت یک مختصات درست و دیگری نه، باگ در نرمال‌سازی است نه در حساب

سومی عادتی است که از منحنی کوتاه‌تر وارد شده. هم اسکالر به‌ازای هر امضا و هم اسکالر چالش باید از digest کامل کاهش یابند، که برای Ed448 ۱۱۴ بایت است، نه از ۵۷ بایت نخستش. منحنی ۳۲ بایتی هم از digest کامل ۶۴ بایتی‌اش استفاده می‌کند، پس قاعده سازگار است؛ تنها فرضی که غلط است این است که «نصف digest عرض اسکالر است»

چهارم ordering است. پیشوند جداسازی دامنه اول می‌آید، پیش از پیشوند context و پیام، که ترتیبی نیست که خوانش شهودی R و A در مشخصات القا می‌کند. اشتباه‌گرفتن این موضوع امضاهایی تولید می‌کند که فقط در برابر پیاده‌سازی خودتان تأیید می‌شوند و هیچ‌چیز دیگر، که گمراه‌کننده‌ترین شکست ممکن است

// طراحی carry میدانی: انتشار با معناشناسی floor خالص، تا هم
// limbهای مثبت و هم منفی کار کنند و تفریق به bias نیاز نداشته باشد.
// carry بالایی از طریق 2^448 = 2^224 + 1 (mod p) برمی‌گردد، که
// به limb 0 و limb 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]);          // floor، نه برش
      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;

نسخه پیشین آن روتین پیش از انتشار یک bias اعمال می‌کرد، و با ورودی‌های بزرگ یک carry کاذب با بزرگی غلط را در limbهای پایین می‌پیچاند. طرح‌های carry مبتنی بر bias منبع پایدار این رده عیب‌اند؛ معناشناسی floor با یک حلقه repeat کران‌دار راحت‌تر استدلال‌پذیر است و به‌طور اندازه‌گیری‌پذیر به‌قدر کافی سریع

دو علت ریشه‌ای در Brainpool

نخستین اصلاً رمزنگاری نیست. نمایش کاری ۳۳ limb است، پس حاصل‌ضرب دو مقدار به ۶۶ نیاز دارد، و آرایه حاصل‌ضرب با ۶۴ اعلان شده بود. نوشتن فراتر از انتها حافظه مجاور را خراب می‌کرد، که نخست به‌شکل نتایج غلط ظاهر شد و تنها وقتی یک اسکن وسیع‌تر اضافه شد crash شد. قاعده‌ای که از آن بیرون آمد شایسته اعمال به هر بافر عددی با اندازه ثابت است: اندازه‌اش را از پهنای حاصل‌ضرب بدترین حالت بگیرید و حاشیه اضافه کنید، و بعد دیگر هرگز به آن فکر نکنید. آرایه در کد انتشار یافته ۶۸ limb است

دومی شکل به‌هم‌ریخته‌ای از توان‌رسانی است. دو فرم درست square-and-multiply وجود دارد و توان را در جهت‌های مخالف مصرف می‌کنند: فرم راست‌به‌چپ ضرب می‌کند و سپس پایه را مربع می‌کند و باید بیت‌ها را از انتهای کم‌ارزش بخواند، در حالی که فرم چپ‌به‌راست مربع می‌کند سپس ضرب و از انتهای پرارزش می‌خواند. حلقه وارون‌سازی پیمانه‌ای بدنه‌ای راست‌به‌چپ با پیمایش بیت پرارزش‌اول داشت. هر دو نیمه کتاب‌درسی‌اند، ترکیب‌شان نه، و نتیجه یک وارون غلط است که همچنان شبیه یک عنصر میدانی موجه به‌نظر می‌رسد

دو شکل توان‌رسانی square-and-multiply با جهت‌های بیت مخالف و فرم مخلوطی که وارون‌های پیمانه‌ای Brainpool را غلط محاسبه می‌کرد
هر دو فرم square-and-multiply به‌خودی‌خود درست‌اند؛ جفت‌کردن بدنه راست‌به‌چپ با پیمایش پرارزش‌اول یک وارون غلط موجه می‌دهد
// دوبرابرسازی و جمع Jacobian وقتی که رکورد مقصد می‌تواند همان
// متغیر یکی از منابع باشد. کپی کل رکورد در ورود تنها دفاع
// مطمئن است: نوشتن limbهای 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;

دو درس فرایندی که بیشتر از خود باگ‌ها خرج داشتند

وصله‌های داغ افزایشی روی یک یونیت رمزنگاری همگرا نمی‌شوند. یک پیش‌نویس بارها وصله شد تا جایی که ۳۲ روتین تکراری و ساختاری آسیب‌دیده با خود حمل می‌کرد، و تنها با بازنویسی‌اش درست شد. الگوی قابل اتخاذ این است: یا یک‌بار از روی یک آینه اعتبارسنجی‌شده بنویسید یا بازنویسی کنید؛ رشته‌ای از اصلاحات موضعی روی حسابی که هنوز نمی‌فهمید سریع‌تر از آنکه اصلاح کند انباشته می‌شود

و پیش از باورکردن نتیجه یک تست، مهر زمانی روی فایل اجرایی را بررسی کنید. یک بیلد افزایشی که کامپایل می‌شود ولی دوباره link نمی‌کند باینری قبلی را اجرا می‌کند، که یک دور کامل سرنخ غلط درباره پروب‌های گمشده و خروجی تکراری ساخت. هنگام دیباگ رمزنگاری، یک نتیجه تبیین‌ناپذیر باید پیش از «آیا الگوریتم غلط است» این پرسش را برانگیزد که «آیا این همان باینری است که همین حالا ساختم»

کارایی، دامنه و نحوه فراخوانی

کاهش پیمانه‌ای در یونیت 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 فهرست شده است