Tehnični članak

Ed448 in Brainpool ECDSA v čistem Pascalu za PDF

PDFlibPas podpisuje in preverja z Ed448 in s tremi krivuljami Brainpool ECDSA v čistem Object Pascalu. Brez zunanje kriptografske knjižnice, brez ponudnika platforme, brez DLL: PDFlibEd448 implementira RFC 8032 PureEdDSA na edwards448, PDFlibBrainpool pa implementira RFC 5639 brainpoolP256r1, brainpoolP384r1 in brainpoolP512r1. Oba sta bila zgrajena na enak način, proti vektorjem znanih odgovorov, generiranim neodvisno, še preden je bil napisan kateri koli Pascal, in vredna pisanja sta predvsem zaradi hroščev

Aritmetika telesa (field arithmetic) je nenavadno iskrena koda. Ali se ujema z objavljenimi vektorji bajt za bajtom ali pa ne, zato ni prostora za »večinoma delujoče«. Težavna postane, ker napačna implementacija še vedno proizvaja podpise, še vedno preverja svoje podpise in še vedno izgleda popolnoma verodostojno

Zakaj te krivulje in zakaj v Pascalu

Krivulje Brainpool se pojavljajo v evropskih profih kvalificiranih podpisov, zato jih knjižnica, ki podpisuje dokumente za ta trg, ne more obravnavati kot eksotične. Ed448 je v naboru algoritmov, ki ga ISO/TS 32002 prinaša v PDF, kjer je njegova notranja prebava (digest) SHAKE256 in ne SHA-2. Nobena od teh družin ni na voljo v splošno uporabljenih kriptografskih knjižnicah za Pascal, zato si jih mora PDF knjižnica, ki jih želi, lastiti sama

Argument za namestitev je isti, ki velja za vso kriptografijo te knjižnice: aplikacija, ki pošilja eno binarno datoteko brez kriptografske odvisnosti, nima ponudnika za odkrivanje, ne različice za usklajevanje in ne vedenja, ki se spremeni, ko je gostitelj popravljen. Podpisovanje je ravno tisto področje, kjer najmanj želite premikajočo se odvisnost

Konstante prihajajo iz besedila specifikacije, nikoli iz spomina

Prvi poskus osnovne točke edwards448 je bil napisan iz spomina in je bil napačen. To ni izjemna napaka in je zelo draga, ker napačna osnovna točka proizvede samoskladni sistem: vaša generacija ključev, podpisovanje in preverjanje se med sabo vsi ujemajo in neujemajo s preostalim svetom

Delovni postopek je, da vsak domen parameter (domain parameter) vzamete iz besedila specifikacije in ga nato navzkrižno preverite. Za edwards448 to pomeni praštevilo, konstanto krivulje, red grupe in obe decimalni koordinati osnovne točke iz RFC 8032, pretvorjene v notranjo predstavitev okončin (limbs), in nato preverjene proti objavljenim testnim vektorjem iz istega dokumenta. Za krivulje Brainpool to pomeni parametre iz RFC 5639, neodvisno implementacijo, napisano za generiranje vektorjev, in navzkrižno preverjanje proti sistemski knjižnici v obeh smereh, preden je tekel kateri koli Pascal

Domen parametri Ed448 in Brainpool tečejo iz besedila specifikacij RFC 8032 in RFC 5639 v obliko okončin in se navzkrižno preverijo, preden zažene kateri koli Pascal
Domen parametri za edwards448 in krivulje Brainpool so vzeti iz besedila RFC, pretvorjeni v okončine in navzkrižno preverjeni proti neodvisnim vektorjem

Ena bližnjica pri izpeljavi si zasluži opozorilo, ker izgleda splošna in ni: rekonstrukcija osnovne točke iz fiksne vrednosti y deluje za krivuljo 25519 in ne deluje za edwards448, kjer ta vrednost nima kvadratnega korena. Skripta jo je ovrgla v sekundah, kar je veliko ceneje, kot da bi jo odkrili skozi razhroščevalnik

Metoda: zrcalna implementacija na ravni okončin pred vsakim Pascalom

Tehnika, ki je naredila obe enoti obvladujoči, je zrcalna implementacija v jeziku z neomejenimi celimi števili, zgrajena od spodaj navzgor. Najprej sama aritmetična plast: množenje telesa, odštevanje in širjenje prenosa (carry propagation), stresno testirana proti njihovim algebraičnim invariantam na nekaj sto naključnih primerih. Nato celotna generacija ključev znotraj zrcala, kjer živijo semantični hrošči in kjer je njihovo odkritje poceni. Šele nato Pascalov prepis

Potek dela zrcalne implementacije z neomejenimi celimi števili, ki preverja Pascalovo aritmetiko telesa in generacijo ključev za Ed448 in Brainpool
Potek zrcalnega dela od spodaj navzgor: najprej aritmetika, nato generacija ključev znotraj zrcala, nato Pascalov prepis in primerjava vmesnih vrednosti

Korist je diagnostična in ne razvojna. Ko je znano, da je zrcalo pravilno, je vsako neskladje med zrcalom in Pascalom spodrsljaj pri prepisu, in sondiranje iste vmesne vrednosti v obeh implementacijah ga takoj locira. To pretvori razred hroščev, ki je sicer skoraj nerazhroščljiv, eno samo napačno okončino globoko znotraj skalarnega množenja, v petminutno primerjavo

Štirje korenski vzroki v Ed448

Vsi štirje so bili odkriti s sondiranjem vmesnih vrednosti in vsi štirje so tiste vrste, ki proizvaja izhod, ki izgleda veljavno

Prvi je past notacije. Večina objavljenih formul za enotno Edwardsovo seštevanje predpostavlja konstanto krivulje minus ena, edwards448 pa ima plus ena. Prenešeno nespremenjeno, števec koordinate y je zapisan kot vsota, kjer bi morala biti razlika. Popravek ni zakrpati predznak, ampak znova izpeljati obliko produkta brez inverzije iz afinega zakona seštevanja za pravo krivuljo, kar proizvede štiri izraze koordinat in ne pusti prostora, da bi bil predznak podedovan iz napačnega vira

Drugi je pri dekompresiji točke. Rekonstrukcija afinega x iz projektivnih koordinat zahteva eno množenje z inverzom Z. Množenje s kvadratom inverza da vrednost, ki je še vedno veljavna projektivna predstavitev in je napačna afina koordinata, zato je simptom pravilen y z napačnim x. Vsakič, ko je ena koordinata pravilna in druga ne, je hrošč v normalizaciji, ne v aritmetiki

Tretji je navada, uvožena iz krajše krivulje. Tako skalar za vsak podpis kot izzivski skalar morata biti reducirana iz popolne prebave, ki je za Ed448 114 bajtov, in ne iz njenih prvih 57. Krivulja 32 bajtov prav tako uporablja svojo celotno 64-bajtno prebavo, zato je pravilo skladno; napačna je samo predpostavka, da je »polovica prebave širina skalara«

Četrti je vrstni red. Predpona ločevanja domen pride prva, pred predpono konteksta in sporočilom, kar ni vrstni red, ki ga nakazuje intuitivno branje R in A v specifikaciji. Napačno to proizvede podpise, ki se preverijo proti vaši lastni implementaciji in ničemer drugemu, kar je najbolj zavajajoča mogoča napaka

// Oblikovanje prenosa telesa: čisto širjenje s semantiko poda (floor), tako
// da delujeta tako pozitivni kot negativni okončini in odštevanje ne
// potrebuje pristranskosti. Zgornji prenos se zvije nazaj skozi
// 2^448 = 2^224 + 1 (mod p), kar se dotakne okončine 0 in okončine 8.
// Omejeno na štiri kroge; dva opažena v praksi
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]);          // pod, ne odrezovanje
      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;

Zgodnejša različica te rutine je uporabila pristranskost pred širjenjem, in pri velikih vhodih je zvila lažni prenos napačne magnitude v nizke okončine. Sheme prenosa na osnovi pristranskosti so vztrajen vir te vrste napak; semantika poda z omejeno zanko ponavljanja je lažja za razumevanje in merljivo dovolj hitra

Dva korenska vzroka v Brainpoolu

Prvi sploh ni kriptografija. Delujoča predstavitev je 33 okončin, zato produkt dveh vrednosti potrebuje 66, polje produktov pa je bilo deklarirano s 64. Pisanje čez konec je pokvarilo sosednji pomnilnik, kar se je najprej pokazalo kot napačni rezultati in je šele z dodanim širšim pregledom postalo sesutje. Pravilo, ki je iz tega nastalo, je vredno uporabiti na vsakem numeričnem predpomnilniku fiksne velikosti: določite velikost iz širine produkta najslabšega primera, dodajte rezervo in o tem nikoli več ne razmišljajte. Polje v izdani kodi je 68 okončin

Drugi je zmešana oblika potenciranja. Obstajata dve pravilni obliki kvadriraj-in-pomnoži (square-and-multiply) in v si eksponent porabljata v nasprotnih smereh: oblika z desne na levo najprej pomnoži in nato kvadrira osnovo ter mora brati bite z najmanj pomembnega konca, oblika z leve na desno pa najprej kvadrira in nato pomnoži ter bere z najbolj pomembnega konca. Zanka modularne inverzije je imela telo z desne na levo z bitnim sprehodom od najbolj pomembnega bita. Obe polovici sta učbeniški, kombinacija pa ni, rezultat pa je napačen inverz, ki še vedno izgleda kot verodostojen element telesa

Dve obliki potenciranja kvadriraj-in-pomnoži z nasprotnima smerma bitov in mešana oblika, ki je izračunala napačne modularne inverze Brainpool
Obli kvadriraj-in-pomnoži sta pravilni vsaka zase; parjenje telesa z desne na levo s sprehodom od najbolj pomembnega bita da verodostojen napačen inverz
// Jacobianovo podvajanje in seštevanje, kjer je lahko ciljni zapis
// ista spremenljivka kot vir. Kopija celotnega zapisa na vstopu je edini
// zanesljiv obrambni mehanizem: pisanje okončin R onesnaži poznejša
// branja P
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
  const Curve: TBPCurve);
var
  Pin: TBPPoint;
begin
  Pin := P;        // najprej kopiraj, nato računaj samo iz Pin
  // ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;

Dve lekciji o procesu, ki sta stale več kot hrošči

Inkrementalno vroče krpanje ne konvergira na kriptografsko enoto. En osnutek je bil večkrat zakrpan, dokler ni nosil 32 podvojenih rutin in poškodovano strukturo, popravljena pa je bila samo s prepisovanjem. Vzorec, ki ga je treba sprejeti, je ali napisati enkrat iz preverjenega zrcala ali ga prepisati; zaporedje lokalnih popravkov aritmetike, ki je še ne razumete, se nabira hitreje, kot jo popravlja

In preverite časovni žig na izvedljivi datoteki, preden verjamete testnemu rezultatu. Inkrementalna gradnja, ki prevede a ne ponovno poveže, poganja prejšnjo binarno datoteko, kar je izmislilo celoten krog lažnih sledi o manjkajočih sondah in podvojenem izhodu. Pri razhroščevanju kriptografije naj nerazložen rezultat sproži vprašanje »ali je to binarna datoteka, ki sem jo pravkar zgradil«, preden »ali je algoritem napačen«

Zmogljivost, obseg in kako ga klicati

Modularna redukcija v enoti Brainpool je bitno serijsko premikanje in odštevanje od najvišjega nastavljenega bita produkta, zato množenje stane približno v redu širine bitov. Preverjanje P-256 pristane v nizkih stotinah milisekund, kar je neopazno za podpisovanje ali preverjanje dokumentov in bi bilo neustrezno za TLS terminator. Barrettova redukcija je očitna nadgradnja in potrebuje širšo delovno vrednost, kot jo nosi trenutna predstavitev, zato je to sprememba, ki jo naredite, ko jo zahteva delovna obremenitev, in ne preventivno

uses
  PDFlibEd448, PDFlibBrainpool;

var
  PublicKey, Signature: AnsiString;
  Curve: TBPCurve;
  R, S, PubX, PubY: TBPValue;
begin
  // Ed448: PureEdDSA, interno SHAKE256, ključi 57 bajtov
  if Ed448PublicKeyFromSeed(Seed, PublicKey) and
     Ed448Sign(DocumentDigest, Seed, Signature) then
    Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));

  // Brainpool: klicatelj preskrbi nonce za vsak podpis, zato
  // politika nonce ostane pri aplikaciji
  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;

Upoštevajte, da vstopna točka podpisovanja Brainpool vzame nonce namesto da ga generira. To je namerno: generacija nonce je najbolj katastrofalna stvar, ki jo lahko v ECDSA pokvarite, ker ponovljena ali napovedljiva vrednost razkrije zasebni ključ, odločitev o tem, od kod prihaja naključnost, pa pripada aplikaciji in njenemu skladnostnemu režimu, ne PDF knjižnici

Te krivulje stojijo ob strani kvantno odpornega dela, opisanega v članku FIPS 204 ML-DSA, in se priklopijo na isti cevovod podpisovanja in validacije, ki ga pokriva PAdES podpisovanje in validacija. Za testna certifikata na teh krivuljah je lokalna pot generacije opisana v samo-podpisanih certifikatih s CryptoAPI. Polna matrika algoritmov je navedena na strani produkta losLab PDF Developer Library