Teknisk artikkel

Ed448 og Brainpool ECDSA i ren Pascal for PDF

PDFlibPas signerer og verifiserer med Ed448 og med de tre Brainpool ECDSA-kurvene i ren Object Pascal. Intet eksternt kryptografisk bibliotek, ingen plattformsprovider, ingen DLL: PDFlibEd448 implementerer RFC 8032 PureEdDSA på edwards448, og PDFlibBrainpool implementerer RFC 5639 brainpoolP256r1, brainpoolP384r1 og brainpoolP512r1. Begge ble bygget på samme måte, mot kjente-svar-vektorer generert uavhengig før noe Pascal var skrevet, og begge er verdt å skrive om først og fremst på grunn av feilene

Kroppsaritmetikken er uvanlig ærlig kode. Den samsvarer enten byte for byte med publiserte vektorer, eller så gjør den ikke det, så det finnes ingen plass til «nesten fungerende». Det som gjør det vanskelig, er at en feil implementering likevel produserer signaturer, likevel verifiserer sine egne signaturer og likevel ser helt plausibel ut

Hvorfor disse kurvene, og hvorfor i Pascal

Brainpool-kurvene opptrer i europeiske profiler for kvalifiserte signaturer, så et bibliotek som signerer dokumenter for det markedet, kan ikke behandle dem som eksotiske. Ed448 er i algoritmesettet som ISO/TS 32002 bringer til PDF, der dens interne digest er SHAKE256 snarere enn SHA-2. Ingen av de to familiene finnes i Pascal-kryptografibibliotekene i vanlig bruk, så et PDF-bibliotek som vil ha dem, må eie dem selv

Utrullingsargumentet er det samme som gjelder for all kryptografien i dette biblioteket: en applikasjon som sender ut én binærfil uten kryptografiske avhengigheter, har ingen provider å oppdage, ingen versjon å matche og ingen oppførsel som endrer seg når vertsmaskinen patches. Signering er nettopp området der du minst ønsker en bevegelig avhengighet

Konstanter kommer fra spesifikasjonsteksten, aldri fra hukommelsen

Det første forsøket på edwards448 basispunkt ble skrevet fra hukommelsen og var feil. Det er ikke en bemerkelsesverdig feil, men den er svært kostbar, fordi et feil basispunkt produserer et selvkonsistent system: nøkkelgenereringen, signeringen og verifiseringen din er alle enige med hverandre og uenige med resten av verden

Prosedyren som fungerer, er å ta hver domeneparameter fra spesifikasjonsteksten og deretter kryssverifisere. For edwards448 betyr det primtallet, kurvekonstanten, gruppeordenen og begge desimalkoordinatene til basispunktet fra RFC 8032, konvertert til den interne limb-representasjonen, og deretter sjekket mot publiserte testvektorer fra det samme dokumentet. For Brainpool-kurvene betyr det parametrene fra RFC 5639, en uavhengig implementering skrevet for å generere vektorer, og en krysssjekk mot et systembibliotek i begge retninger før noe Pascal kjørte

Ed448- og Brainpool-domeneparametere flyter fra RFC 8032- og RFC 5639-spesifikasjonsteksten inn i limb-form og kryssverifiseres før noe Pascal kjører
Domeneparameterne for edwards448 og Brainpool-kurvene hentes fra RFC-tekst, konverteres til limbs og kryssverifiseres mot uavhengige vektorer

Én avledningssnarvei fortjener en advarsel fordi den ser universell ut og ikke er det: å gjenopprette basispunktet fra en fast y-verdi fungerer for 25519-kurven og fungerer ikke for edwards448, der den verdien ikke har noen kvadratrot. Et skript motbeviste det på sekunder, noe som er mye rimeligere enn å oppdage det gjennom en feilsøker

Metoden: en speiling på limb-nivå før noe Pascal

Teknikken som gjorde begge enhetene håndterbare, er en speilimplementering i et språk med ubegrensede heltall, bygget nedefra og opp. Først aritmetikklaget alene: kroppsmultiplikasjon, subtraksjon og carry-propagering, stresstestet mot sine algebraiske invarianter over et par hundre tilfeldige tilfeller. Deretter hele nøkkelgenereringen inne i speilingen, som er der de semantiske feilene bor og der de er billige å finne. Først deretter Pascal-transkripsjonen

Arbeidsflyt for en speilimplementering med ubegrensede heltall som validerer Pascal kroppsaritmetikk og nøkkelgenerering for Ed448 og Brainpool
Speilingsarbeidsflyten nedefra og opp: først aritmetikk, deretter nøkkelgenerering inne i speilingen, så Pascal-transkripsjonen og sammenligning av mellomverdier

Ubyttet er diagnostisk snarere enn utviklingsmessig. Når speilingen er kjent korrekt, er enhver uenighet mellom speiling og Pascal en transkripsjonsfeil, og å undersøke den samme mellomverdien i begge implementeringene lokaliserer den umiddelbart. Det omdanner en klasse feil som ellers er nesten ufeilsøkbar, en enkelt feil limb dypt inne i en skalarmultiplikasjon, til en sammenligning på fem minutter

Fire rotårsaker i Ed448

Alle fire ble funnet ved å undersøke mellomverdier, og alle fire er av den typen som produserer output som ser gyldig ut

Den første er en notasjonsfelle. De fleste publiserte formler for uniform Edwards-addisjon antar en kurvekonstant på minus én, og edwards448 har pluss én. Overført uendret skrives telleren i y-koordinaten som en sum der den skulle vært en differanse. Fiksen er ikke å lappe fortegnet, men å utlede den inversfrie produktformen på nytt fra den affine addisjonsloven for den riktige kurven, noe som produserer de fire koordinatuttrykkene og ikke etterlater rom for at et fortegn kan arves fra feil kilde

Den andre ligger i punktdekompresjon. Å gjenopprette den affine x fra projektive koordinater krever én multiplikasjon med inversen av Z. Å multiplisere med den kvadrerte inversen gir en verdi som fortsatt er en gyldig projektiv representasjon og er den feil affine koordinaten, så symptomet er en korrekt y med en feil x. Hver gang én koordinat er riktig og den andre ikke er det, sitter feilen i normaliseringen, ikke i aritmetikken

Den tredje er en vane importert fra den kortere kurven. Både skalareren per signatur og utfordringsskalareren må reduseres fra det komplette digestet, som for Ed448 er 114 byte, ikke fra de første 57. 32-byte-kurven bruker også sitt fulle 64-byte-digest, så regelen er konsistent; det er bare antakelsen om at «halve digestet er skalarbredden» som er feil

Den fjerde er rekkefølgen. Domeneskillingsprefikset kommer først, før kontekstprefikset og meldingen, noe som ikke er rekkefølgen den intuitive lesingen av R og A i spesifikasjonen antyder. Å ta dette feil produserer signaturer som verifiserer mot din egen implementering og ingenting annet, noe som er den mest villedende tenkelige feilen

// Design av kroppscarry: ren propagering med gulvsemantikk, slik at
// både positive og negative limbs fungerer og subtraksjon ikke trenger
// noe bias. Topp-carryen foldes tilbake gjennom 2^448 = 2^224 + 1 (mod p),
// noe som berører limb 0 og limb 8. Avgrenset til fire runder; to
// observert i praksis
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]);          // gulv, ikke avkorting
      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;

En tidligere versjon av den rutinen anvendte et bias før propagering, og ved store inndata foldet den en falsk carry av feil størrelsesorden inn i de lave limbene. Bias-baserte carry-ordninger er en vedvarende kilde til denne klassen av defekter; gulvsemantikk med en avgrenset repeat-løkke er enklere å resonnere om og målbar rask nok

To rotårsaker i Brainpool

Den første er i det hele tatt ikke kryptografi. Den fungerende representasjonen er 33 limbs, så produktet av to verdier trenger 66, og produktarrayet ble deklarert med 64. Å skrive forbi slutten korrupterte tilstøtende minne, noe som først viste seg som feil resultater og først ble et krasj da en bredere skanning ble lagt til. Regelen som kom ut av det, er verdt å anvende på hver numeriske buffer med fast størrelse: dimensjoner den etter produktbredden i verste fall og legg til margin, og tenk aldri på det igjen. Arrayet i leveringskoden er 68 limbs

Den andre er en forvekslet eksponentieringsform. Det finnes to korrekte square-and-multiply-former, og de konsumerer eksponenten i motsatte retninger: høyre-til-venstre-formen multipliserer og kvadrerer deretter basen og må lese biter fra den minst signifikante enden, mens venstre-til-høyre-formen kvadrerer og multipliserer deretter og leser fra den mest signifikante enden. Den modulare inverseringsløkken hadde en høyre-til-venstre-kropp med en bitgjennomgang som gikk mest-signifikant-først. Begge halvdeler er lærebokstoff, kombinasjonen er det ikke, og resultatet er en feil invers som fortsatt ser ut som et plausibelt kroppselement

To square-and-multiply-eksponentieringsformer med motsatte bitretninger og den blandede formen som beregnet feil modulære Brainpool-inverser
Begge square-and-multiply-formene er korrekte hver for seg; en høyre-til-venstre-kropp parret med en mest-signifikant-først-gjennomgang gir en plausibel feil invers
// Dobling og addisjon i Jacobian-form der målrecorden kan være den
// samme variabelen som en kilde. En kopi av hele recorden ved inngang
// er det eneste pålitelige forsvaret: å skrive Rs limbs forurenser
// senere lesinger av P
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
  const Curve: TBPCurve);
var
  Pin: TBPPoint;
begin
  Pin := P;        // kopier først, og beregn deretter bare fra Pin
  // ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;

To prosessleksjoner som kostet mer enn feilene

Inkrementell hurtigfiksing konvergerer ikke på en kryptografisk enhet. Ett utkast ble lappet gjentatte ganger til det bar på 32 dupliserte rutiner og en skadet struktur, og det ble bare fikset ved å skrive det om. Mønsteret å ta i bruk, er enten å skrive det én gang fra en validert speiling eller å skrive det om; en sekvens av lokale fikser på aritmetikk du ennå ikke forstår, akkumulerer raskere enn den retter

Og sjekk tidsstempelet på den kjørbare filen før du tror på et testresultat. Et inkrementelt bygg som kompilerer, men ikke lenker på nytt, kjører den forrige binærfilen, noe som fabrikkerte en hel runde med falske spor om manglende probes og duplisert output. Ved feilsøking av kryptografi bør et uforklarlig resultat utløse spørsmålet «er dette binærfilen jeg nettopp bygde» før «er algoritmen feil»

Ytelse, omfang og hvordan du kaller det

Modulær reduksjon i Brainpool-enheten er bitvis seriell skift-og-trekk fra den høyeste satte biten i produktet, så en multiplikasjon koster grovt sett i størrelsesordenen av bitbredden. En P-256-verifisering havner et sted i de lavere hundre millisekundene, noe som er helt greit for signering eller verifisering av dokumenter og ville være utilstrekkelig for en TLS-terminator. Barrett-reduksjon er den opplagte oppgraderingen og trenger en bredere arbeidsverdi enn den nåværende representasjonen bærer, så det er en endring man gjør når en arbeidsmengde ber om det, snarere enn forebyggende

uses
  PDFlibEd448, PDFlibBrainpool;

var
  PublicKey, Signature: AnsiString;
  Curve: TBPCurve;
  R, S, PubX, PubY: TBPValue;
begin
  // Ed448: PureEdDSA, SHAKE256 internt, 57-byte-nøkler
  if Ed448PublicKeyFromSeed(Seed, PublicKey) and
     Ed448Sign(DocumentDigest, Seed, Signature) then
    Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));

  // Brainpool: kalleren leverer noncen per signatur, så nonce-
  // politikken blir værende hos applikasjonen
  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;

Merk at Brainpoolens signeringsinngangspunkt tar imot noncen i stedet for å generere én. Det er bevisst: nonce-generering er den aller mest katastrofale tingen å ta feil på i ECDSA, siden en gjentatt eller forutsigbar verdi avslører den private nøkkelen, og avgjørelsen om hvor tilfeldigheten kommer fra, tilhører applikasjonen og dens etterlevelsesregime, ikke et PDF-bibliotek

Disse kurvene står side om side med post-kvantum-arbeidet som er beskrevet i FIPS 204 ML-DSA-artikkelen, og de kobles inn i den samme signerings- og valideringspipelinen som dekkes i PAdES-signering og -validering. For testsertifikater på disse kurvene er den lokale genereringsruten beskrevet i selvsignerte sertifikater med CryptoAPI. Den komplette algoritmematrisen er oppført på produktsiden for losLab PDF Developer Library