Technický článek

Ed448 a Brainpool ECDSA v čistém Pascalu pro PDF

PDFlibPas podepisuje a ověřuje pomocí Ed448 a tří křivek Brainpool ECDSA v čistém Object Pascalu. Žádná externí kryptografická knihovna, žádný platformový provider, žádná DLL: PDFlibEd448 implementuje PureEdDSA z RFC 8032 na křivce edwards448 a PDFlibBrainpool implementuje brainpoolP256r1, brainpoolP384r1 a brainpoolP512r1 z RFC 5639. Obě jednotky vznikly stejným způsobem, proti známým testovacím vektorům generovaným nezávisle dříve, než byl napsán jediný řádek Pascalu, a za zmínku stojí především kvůli chybám

Aritmetika těles je neobvykle upřímný kód. Buď odpovídá publikovaným vektorům bajt za bajtem, nebo neodpovídá, takže prostor pro „téměř funkční“ zde není. Ztěžuje to okolnost, že chybná implementace stále generuje podpisy, stále ověřuje vlastní podpisy a stále vypadá zcela důvěryhodně

Proč právě tyto křivky a proč v Pascalu

Křivky Brainpool se objevují v evropských profilech kvalifikovaných podpisů, takže knihovna, která pro tento trh podepisuje dokumenty, je nemůže považovat za exotiku. Ed448 patří do sady algoritmů, kterou ISO/TS 32002 přináší do PDF, přičemž jeho interním hašem je SHAKE256 namísto SHA-2. Ani jedna z těchto rodin není k dispozici v běžně používaných kryptografických knihovnách pro Pascal, takže si je PDF knihovna, která je chce využívat, musí implementovat sama

Argument pro nasazení je stejný jako u veškeré kryptografie této knihovny: aplikace, která dodává jedinou binárku bez kryptografické závislosti, nemá žádný provider k detekci, žádnou verzi ke sladění a žádné chování, které by se změnilo při záplatování hostitelského systému. Podepisování je přesně tou oblastí, kde se pohyblivá závislost hodí nejméně ze všeho

Konstanty pocházejí z textu specifikace, nikdy z paměti

První pokus o základní bod edwards448 byl zapsán z paměti a byl chybný. Nejde o nijak pozoruhodnou chybu, zato je velmi drahá, protože chybný základní bod vytváří soběstačně konzistentní systém: vaše generování klíčů, podepisování i ověřování se navzájem shodují a s okolním světem nesouhlasí

Funkční postup spočívá v tom, že každý doménový parametr převezmete z textu specifikace a poté jej křížově ověříte. U edwards448 to znamená prvočíslo, konstantu křivky, řád grupy a obě desetinné souřadnice základního bodu z RFC 8032, převedené do interní reprezentace limbů, a následnou kontrolu proti publikovaným testovacím vektorům z téhož dokumentu. U křivek Brainpool jde o parametry z RFC 5639, nezávisle napsanou implementaci určenou ke generování vektorů a obousměrné křížové ověření proti systémové knihovně, než se vůbec spustil jakýkoli Pascal

Doménové parametry Ed448 a Brainpool přecházejí z textu specifikací RFC 8032 a RFC 5639 do podoby limbů a jsou křížově ověřeny dříve, než se spustí jakýkoli Pascal
Doménové parametry křivek edwards448 a Brainpool jsou převzaty z textu RFC, převedeny na limb a křížově ověřeny proti nezávislým vektorům

Jedna zkratka při odvozování si zaslouží varování, protože působí univerzálně, ale není: obnovení základního bodu z pevné hodnoty y funguje u křivky 25519, zatímco u edwards448 ne, protože daná hodnota nemá druhou odmocninu. Skript to vyvrátil během několika sekund, což je mnohem levnější než odhalování v debuggeru

Metoda: zrcadlová implementace na úrovni limbů před jakýmkoli Pascalem

Technikou, díky níž byly obě jednotky zvládnutelné, je zrcadlová implementace v jazyce s neomezenými celými čísly, stavěná zdola nahoru. Nejprve samotná aritmetická vrstva: násobení a odčítání v tělese a šíření přenosů, zatížené testy proti algebraickým invariantům na několika stovkách náhodných případů. Potom plné generování klíčů uvnitř zrcadla, protože právě tam sídlí sémantické chyby a právě tam je jejich odhalení levné. Teprve potom přepis do Pascalu

Workflow zrcadlové implementace s neomezenými celými čísly ověřující aritmetiku těles a generování klíčů v Pascalu pro Ed448 a Brainpool
Workflow zrcadla od základů: nejprve aritmetika, poté generování klíčů uvnitř zrcadla a nakonec přepis do Pascalu a porovnávání mezivýsledků

Přínos je spíše diagnostický než vývojový. Jakmile je zrcadlo známo jako správné, každá neshoda mezi zrcadlem a Pascalem je přepisovým překlepem a prozkoumání téhož mezivýsledku v obou implementacích jej okamžitě lokalizuje. Tím se třída chyb, která je jinak téměř neladitelná — jediný chybný limb hluboko uvnitř skalárního násobení — mění na pětiminutové porovnání

Čtyři kořenové příčiny v Ed448

Všechny čtyři byly odhaleny sondováním mezivýsledků a všechny čtyři jsou typem chyby, která produkuje výstup vypadající jako platný

První je past v notaci. Většina publikovaných vzorců pro unifikované sčítání na Edwardsových křivkách předpokládá konstantu křivky minus jedna, zatímco edwards448 má plus jedna. Při přenesení beze změny se čitatel souřadnice y zapíše jako součet tam, kde má být rozdíl. Oprava nespočívá v úpravě znaménka, ale v opětovném odvození tvaru součinu bez inverze z afinního sčítacího zákona pro správnou křivku, což vytvoří všechny čtyři souřadnicové výrazy a neponechá prostor, aby se znaménko zdědilo ze špatného zdroje

Druhá se týká dekomprese bodu. Obnovení afinní souřadnice x z projektivních souřadnic vyžaduje jedno násobení inverzí hodnoty Z. Násobení čtvercem inverze dá hodnotu, která je stále platnou projektivní reprezentací, ale jde o špatnou afinní souřadnici, takže příznakem je správné y se špatným x. Kdykoli je jedna souřadnice správná a druhá ne, chyba je v normalizaci, nikoli v aritmetice

Třetí je návyk přenesený z kratší křivky. Jak skalár jednotlivého podpisu, tak challenge skalár musí být redukovány z kompletního hashe, který má u Ed448 114 bajtů, a nikoli z jeho prvních 57. Dvaatřicetibajtová křivka používá plný šedesátibajtový hash taktéž, takže pravidlo je konzistentní; chybný je pouze předpoklad, že „polovina hashe je šířka skaláru“

Čtvrtá je pořadí. Prefix oddělení domén přichází jako první, před prefixem kontextu a zprávou, což není pořadí, které naznačuje intuitivní čtení R a A ve specifikaci. Chyba zde vytváří podpisy, které ověří vaše vlastní implementace a nic jiného, a to je možné nejvíce klamné selhání

// Návrh přenosů v tělese: čisté šíření se sémantikou floor, takže
// fungují kladné i záporné limby a odčítání nepotřebuje bias.
// Nejvyšší přenos se vrací zpět přes 2^448 = 2^224 + 1 (mod p), což
// zasahuje limb 0 a limb 8. Omezeno na čtyři průchody; v praxi byly
// pozorovány dva
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, nikoli oříznutí
      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;

Starší verze této rutiny aplikovala bias před šířením a při velkých vstupech zapracovala do spodních limbů falešný přenos špatné velikosti. Schémata přenosů založená na biasu jsou vytrvalým zdrojem této třídy defektů; sémantika floor s ohraničenou opakovanou smyčkou se snáze uvažuje a je měřitelně dostatečně rychlá

Dvě kořenové příčiny v Brainpool

První není vůbec kryptografií. Funkční reprezentace má 33 limbů, takže součin dvou hodnot potřebuje 66 a pole součinu bylo deklarováno s 64. Zápis za konec pole poškodil přilehlou paměť, což se nejprve projevovalo jako chybné výsledky a teprve po přidání širšího skenu se stalo pádem aplikace. Pravidlo, které z toho vzešlo, stojí za uplatnění u každého číselného bufferu pevné velikosti: nastavte velikost podle nejhorší možné šířky součinu a přidejte rezervu, a pak na to už nikdy nemyslete. Pole v dodávaném kódu má 68 limbů

Druhou je pomíchaný tvar umocňování. Existují dva správné tvary square-and-multiply a exponent zpracovávají v opačných směrech: forma zprava doleva násobí a pak umocňuje základ a musí číst bity od nejméně významného konce, zatímco forma zleva doprava umocňuje a pak násobí a čte od nejvýznamnějšího konce. Smyčka modulární inverze měla tělo typu zprava doleva s průchodem bity od nejvýznamnějšího. Obě poloviny jsou učebnicové, kombinace nikoli, a výsledkem je chybná inverze, která přesto vypadá jako věrohodný prvek tělesa

Dva tvary umocňování square-and-multiply s opačnými směry bitů a smíšený tvar, který počítal chybné modulární inverze Brainpool
Oba tvary square-and-multiply jsou samy o sobě správné; spojení těla zprava doleva s průchodem od nejvýznamnějšího bitu dává věrohodnou chybnou inverzi
// Jakobiánské zdvojnásobení a sčítání, kde cílový záznam může být
// tatáž proměnná jako zdroj. Zkopírování celého záznamu na vstupu je
// jediná spolehlivá obrana: zápis do limbů R znečistí pozdější čtení P
procedure BPPointDouble(var R: TBPPoint; const P: TBPPoint;
  const Curve: TBPCurve);
var
  Pin: TBPPoint;
begin
  Pin := P;        // nejprve kopie, poté výpočet pouze z Pin
  // ... M = 3X^2 + A*Z^4, S = 4*X*Y^2, X3 = M^2 - 2S, ...
end;

Dvě lekce procesu, které stály víc než samotné chyby

Inkrementální hotfixování nekonverguje u kryptografické jednotky. Jeden koncept byl opakovaně záplatován, dokud nenesl 32 duplikovaných rutin a poškozenou strukturu, a napraven byl pouze přepsáním. Vzor, který je třeba přijmout, zní: buď jednou napište z validovaného zrcadla, nebo kód přepište; řada lokálních oprav aritmetiky, které ještě nechápete, se hromadí rychleji, než opravuje

A ověřte časové razítko spustitelného souboru, než uvěříte výsledku testu. Inkrementální sestavení, které zkompiluje, ale neprovede relink, spouští předchozí binárku, což vyrobilo celé kolo falešných stop o chybějících sondách a duplikovaném výstupu. Při ladění kryptografie by nevysvětlitelný výsledek měl vyvolat otázku „je to binárka, kterou jsem právě sestavil“ dříve než „je algoritmus špatný“

Výkon, rozsah a způsob volání

Modulární redukce v jednotce Brainpool je bitově sériové odečítání s posuvem od nejvyššího nastaveného bitu součinu, takže násobení stojí řádově zhruba šířku bitu. Ověření P-256 se pohybuje v nižších stovkách milisekund, což je u podepisování a ověřování dokumentů běžně akceptovatelné, kdežto pro TLS terminátor by to nestačilo. Barrettova redukce je zjevný upgrade a vyžaduje širší pracovní hodnotu, než jakou aktuální reprezentace nese, takže jde o změnu, kterou provést, až si ji vyžádá pracovní zátěž, nikoli preventivně

uses
  PDFlibEd448, PDFlibBrainpool;

var
  PublicKey, Signature: AnsiString;
  Curve: TBPCurve;
  R, S, PubX, PubY: TBPValue;
begin
  // Ed448: PureEdDSA, interně SHAKE256, 57bajtové klíče
  if Ed448PublicKeyFromSeed(Seed, PublicKey) and
     Ed448Sign(DocumentDigest, Seed, Signature) then
    Assert(Ed448Verify(DocumentDigest, PublicKey, Signature));

  // Brainpool: nonce pro jednotlivý podpis dodává volající, takže
  // politika nonce zůstává v aplikaci
  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;

Všimněte si, že vstupní bod pro podepisování Brainpool přebírá nonce, místo aby jej generoval. Je to záměrné: generování nonce je v ECDSA tou nejvíce katastrofickou věcí, kterou lze pokazit, protože opakovaná či předvídatelná hodnota prozradí soukromý klíč, a rozhodnutí o tom, odkud náhodnost pochází, patří aplikaci a jejímu režimu shody s předpisy, nikoli PDF knihovně

Tyto křivky stojí vedle postkvantové práce popsané v článku o FIPS 204 ML-DSA a zapojují se do téhož podpisového a validačního potrubí, které pokrývá podepisování a ověřování PAdES. Postup místního generování testovacích certifikátů na těchto křivkách popisuje článek o vlastnoručně podepsaných certifikátech s CryptoAPI. Kompletní matici algoritmů uvádí stránka produktu losLab PDF Developer Library