PDFlibPas semnează și verifică cu Ed448 și cu cele trei curbe Brainpool ECDSA în Object Pascal pur. Fără bibliotecă criptografică externă, fără furnizor de platformă, fără DLL: PDFlibEd448 implementează RFC 8032 PureEdDSA pe edwards448, iar PDFlibBrainpool implementează brainpoolP256r1, brainpoolP384r1 și brainpoolP512r1 din RFC 5639. Ambele au fost construite la fel: pe baza unor vectori known-answer generați independent înainte de a fi scris vreun cod Pascal, și ambele merită prezentate în principal din cauza erorilor
Aritmetica în corpuri finite este un cod neobișnuit de sincer. Se potrivește fie octet cu octet cu vectorii publicați, fie nu, astfel încât nu există loc pentru un „funcționează în mare parte". Ce o face dificilă este faptul că o implementare greșită produce totuși semnături, își verifică totuși propriile semnături și pare în continuare complet plauzibilă
De ce aceste curbe și de ce în Pascal
Curbele Brainpool apar în profilurile europene de semnătură calificată, deci o bibliotecă care semnează documente pentru această piață nu le poate trata drept exotice. Ed448 face parte din setul de algoritmi pe care ISO/TS 32002 îl aduce în PDF, unde digestul intern este SHAKE256 și nu SHA-2. Niciuna dintre cele două familii nu este disponibilă în bibliotecile criptografice Pascal de uz curent, astfel încât o bibliotecă PDF care le dorește trebuie să le implementeze singură
Argumentul de implementare este același care se aplică întregii criptografii a acestei biblioteci: o aplicație care livrează un singur executabil fără nicio dependență criptografică nu are furnizor de detectat, nu are versiune de corelat și nu are niciun comportament care se schimbă când sistemul gazdă primește patch-uri. Semnarea este exact domeniul în care vreți cel mai puțin o dependență mobilă
Constantele provin din textul specificației, niciodată din memorie
Prima încercare pentru punctul de bază edwards448 a fost scrisă din memorie și a fost greșită. Nu este o eroare remarcabilă, dar este una foarte costisitoare, deoarece un punct de bază greșit produce un sistem autoconsistent: generarea cheilor, semnarea și verificarea dumneavoastră sunt de acord între ele și contrazic restul lumii
Procedura care funcționează este să luați fiecare parametru de domeniu din textul specificației, apoi să îl verificați încrucișat. Pentru edwards448 aceasta înseamnă numărul prim, constanta curbei, ordinul grupului și ambele coordonate zecimale ale punctului de bază din RFC 8032, convertite în reprezentarea internă pe membri, apoi verificate cu vectorii de test publicați în același document. Pentru curbele Brainpool înseamnă parametrii din RFC 5639, o implementare independentă scrisă pentru generarea vectorilor și o verificare încrucișată cu o bibliotecă de sistem în ambele direcții înainte să ruleze vreun cod Pascal
O scurtătură de derivare merită un avertisment, deoarece pare universală și nu este: recuperarea punctului de bază dintr-o valoare y fixă funcționează pentru curba 25519 și nu funcționează pentru edwards448, unde acea valoare nu are rădăcină pătrată. Un script a infirmat-o în câteva secunde, ceea ce este mult mai ieftin decât descoperirea prin debugger
Metoda: o implementare-oglindă la nivel de membri înainte de orice Pascal
Tehnica care a făcut ambele unități gestionabile este o implementare-oglindă într-un limbaj cu întregi nelimitați, construită de jos în sus. Mai întâi doar stratul aritmetic: înmulțirea în corp, scăderea și propagarea reporturilor, testate la limită împotriva invariantelor lor algebrice pe câteva sute de cazuri aleatorii. Apoi generarea completă a cheilor în interiorul oglinzii, acolo unde trăiesc erorile semantice și unde sunt ieftin de găsit. Abia apoi transcrierea în Pascal
Beneficiul este de diagnostic, nu de dezvoltare. Odată ce oglinda este cunoscută ca fiind corectă, orice divergență între oglindă și Pascal este o greșeală de transcriere, iar sondarea aceleiași valori intermediare în ambele implementări o localizează imediat. Astfel, o clasă de erori altfel aproape indepanabile — un singur membru greșit în adâncul unei înmulțiri scalare — se transformă într-o comparație de cinci minute
Patru cauze rădăcină în Ed448
Toate patru au fost găsite prin sondarea valorilor intermediare, iar toate patru sunt de tipul care produce rezultate cu aspect valid
Prima este o capcană de notație. Majoritatea formulărilor publicate pentru adunarea Edwards unificată presupun o constantă a curbei de minus unu, iar edwards448 are plus unu. Preluată neschimbată, fracția superioară a coordonatei y se scrie ca sumă acolo unde ar trebui diferență. Soluția nu este corectarea semnului, ci rederivarea formei de produs fără inversare din legea adunării afine pentru curba corectă, ceea ce produce cele patru expresii ale coordonatelor și nu lasă loc ca un semn să fie moștenit din sursa greșită
A doua se află în decomprimarea punctelor. Recuperarea coordonatei afine x din coordonate proiective cere o singură înmulțire cu inversul lui Z. Înmulțirea cu inversul la pătrat produce o valoare care este încă o reprezentare proiectivă validă, dar este coordonata afină greșită, deci simptomul este un y corect cu un x greșit. Oricând o coordonată este corectă, iar cealaltă nu, eroarea este în normalizare, nu în aritmetică
A treia este un obicei importat de la curba mai scurtă. Atât scalarul per semnătură, cât și scalarul de provocare trebuie reduși din digestul complet, care pentru Ed448 este de 114 octeți, nu din primii săi 57. Curba de 32 de octeți folosește la fel întregul digest de 64 de octeți, deci regula este consistentă; greșită este doar presupunerea conform căreia „jumătate din digest este lățimea scalarului"
A patra este ordinea. Prefixul de separare a domeniului vine primul, înaintea prefixului de context și a mesajului, ceea ce nu este ordinea pe care o sugerează lectura intuitivă a lui R și A din specificație. O greșeală aici produce semnături care se verifică doar cu implementarea dumneavoastră și cu nimic altceva, ceea ce este cel mai înșelător eșec posibil
// Proiectarea reporturilor în corp: propagare cu semantică floor pură,
// astfel încât funcționează atât membrii pozitivi, cât și cei negativi,
// iar scăderea nu are nevoie de bias.
// Reportul de vârf se pliază înapoi prin 2^448 = 2^224 + 1 (mod p), ceea ce
// atinge membrul 0 și membrul 8. Limitat la patru treceri; în practică
// s-au observat două
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, nu trunchiere
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;
O versiune anterioară a acelei rutine aplica un bias înainte de propagare, iar la intrări mari plia un report fals de magnitudine greșită în membrii de jos. Schemele de report bazate pe bias sunt o sursă persistentă a acestei clase de defecte; semantică floor cu o buclă repeat mărginită este mai ușor de raționat și suficient de rapidă în mod măsurabil
Două cauze rădăcină în Brainpool
Prima nu este deloc criptografie. Reprezentarea de lucru este de 33 de membri, deci produsul a două valori are nevoie de 66, iar vectorul de produs a fost declarat cu 64. Scrierea peste sfârșit a corupt memoria adiacentă, ceea ce s-a manifestat inițial ca rezultate greșite și a devenit abia mai târziu un crash, odată cu adăugarea unei scanări mai ample. Regula rezultată merită aplicată fiecărui buffer numeric de mărime fixă: dimensionați-l după lățimea produsului din cel mai rău caz, adăugați o marjă, apoi nu vă mai gândiți niciodată la el. Vectorul din codul livrat are 68 de membri
A doua este o formă de exponențiere amestecată. Există două forme corecte de tip square-and-multiply și ele consumă exponentul în direcții opuse: forma dreapta-stânga înmulțește apoi ridică la pătrat baza și trebuie să citească biții de la capătul mai puțin semnificativ, în timp ce forma stânga-dreapta ridică la pătrat apoi înmulțește și citește de la capătul cel mai semnificativ. Bucla de inversare modulară avea un corp dreapta-stânga cu o parcurgere a biților de la cel mai semnificativ. Ambele jumătăți sunt de manual, combinația nu este, iar rezultatul este un invers greșit care tot arată ca un element de corp plauzibil
// Dublare și adunare Jacobian când înregistrarea destinație poate fi
// aceeași variabilă ca o sursă. O copie integrală a înregistrării la
// intrare este singura apărare de încredere: scrierea membrilor lui R
// poluează citirile ulterioare ale lui P
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
const Curve: TBPCurve);
var
Pin: TBPPoint;
begin
Pin := P; // copiază mai întâi, apoi calculează doar din Pin
// ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;
Două lecții de proces care au costat mai mult decât erorile
Corecțiile incrementale pe loc nu converg pentru o unitate criptografică. O ciornă a primit patch-uri repetate până a ajuns să conțină 32 de rutine duplicate și o structură deteriorată, iar singura remediere a fost rescrierea ei. Modelul de adoptat este să îl scrieți o singură dată pornind de la o oglindă validată sau să îl rescrieți; o succesiune de corecții locale aplicate unei aritmetici pe care nu o înțelegeți încă se acumulează mai repede decât se corectează
Și verificați marca temporală a executabilului înainte să credeți un rezultat de test. Un build incremental care compilează, dar nu face relink rulează binarul anterior, ceea ce a fabricat un întreg val de piste false despre sonde lipsă și rezultat duplicat. Când depanați criptografie, un rezultat neexplicat ar trebui să declanșeze întrebarea „acesta este binarul pe care tocmai l-am construit?" înainte de „algoritmul este greșit?"
Performanță, scop și modul de apelare
Reducerea modulară în unitatea Brainpool este de tip shift-subtract bit cu bit pornind de la cel mai semnificativ bit setat al produsului, deci o înmulțire costă aproximativ de ordinul lățimii în biți. O verificare P-256 se încadrează în primele sute de milisecunde, ceea ce este nesemnificativ pentru semnarea sau verificarea documentelor și ar fi inadecvat pentru un terminator TLS. Reducerea Barrett este actualizarea evidentă și cere o valoare de lucru mai lată decât cea din reprezentarea actuală, deci este o schimbare de făcut atunci când o sarcină de lucru o cere, nu preventiv
uses
PDFlibEd448, PDFlibBrainpool;
var
PublicKey, Signature: AnsiString;
Curve: TBPCurve;
R, S, PubX, PubY: TBPValue;
begin
// Ed448: PureEdDSA, SHAKE256 intern, chei de 57 de octeți
if Ed448PublicKeyFromSeed(Seed, PublicKey) and
Ed448Sign(DocumentDigest, Seed, Signature) then
Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));
// Brainpool: apelantul furnizează nonce-ul per semnătură, astfel
// încât politica pentru nonce rămâne la nivelul aplicației
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;
Rețineți că punctul de intrare pentru semnarea Brainpool primește nonce-ul în loc să îl genereze. Este intenționat: generarea nonce-ului este cel mai catastrofal lucru care poate fi greșit în ECDSA, deoarece o valoare repetată sau previzibilă dezvăluie cheia privată, iar decizia privind proveniența aleatorietății aparține aplicației și regimului ei de conformitate, nu unei biblioteci PDF
Aceste curbe se alătură lucrării post-quantum descrise în articolul despre FIPS 204 ML-DSA și se conectează la același pipeline de semnare și validare tratat în semnarea și validarea PAdES. Pentru certificatele de test pe aceste curbe, calea de generare locală este descrisă în certificatele auto-semnate cu CryptoAPI. Matricea completă de algoritmi este listată pe pagina de produs losLab PDF Developer Library