PDFlibPas implementuje ML-DSA, algoritmus digitálneho podpisu založený na modulových mriežkach štandardizovaný vo FIPS 204, úplne v Object Pascale. Všetky tri sady parametrov sa dodávajú ako bežné funkcie: MLDSA44Sign, MLDSA65Sign, MLDSA87Sign, plus zodpovedajúce KeyGen a Verify vstupy. Žiadny OpenSSL, žiadna platformová DLL, žiadne C lepidlo. Jediná jednotka PDFlibMLDSA nespolieha na nič okrem SHAKE špongie knižnice a jej výstup sa zhoduje s oficiálnymi známymi testovacími vektormi FIPS 204 bajt po bajte
Ta posledná veta je jediná časť, ktorá si vyžiadala skutočnú prácu. Napísať mriežkovú aritmetiku v Pascale je mechanické; dosiahnuť zhodu s NIST nie. Nasleduje inžiniersky rozkaz portácie: ako tri sady parametrov skončili pri jednom zdieľanom engine a konkrétne defekty, ktoré delili skompiluje sa a beží od zhoduje sa s KAT. Ak hodnotíte postkvantové možnosti pre dokumentový pipeline v Delphi alebo C++Builder, defekty sú tá užitočná časť, pretože každý z nich produkuje presvedčivo vyzerajúci výstup, ktorý mlčky zlyháva v interoperabilite
Prečo písať postkvantový podpisovač v čistom Object Pascale?
Pretože alternatíva je jedna natívna závislosť na cieľ, a Delphi PDF knižnica ich už má dosť. PDFlibPas sa zostavuje naprieč Delphi, C++Builder a FPC/Lazarus na Win32, Win64 a Unix cieľoch; napojenie C postkvantovej knižnice by znamenalo sledovať jej zostavenie pre každý z týchto slotov, plus povrch konvencií volania a vlastníctva pamäte medzi nimi. Čistá Pascal jednotka sa kompiluje všade, kde sa kompiluje zvyšok knižnice, a to je celý argument
ML-DSA to robí neobyčajne lacným, pretože jeho jediná primitívna závislosť je SHAKE. Nie je tu vrstva veľkých celých čísel, žiadna eliptická krivka, žiadny samostatný hash balík. PDFlibPas získal streaming XOF v vydaní tesne pred portáciou: TPLShakeXOF v PDFlibDigest, kde PLShakeXOFInit vyberá SHAKE128 (rate 168) alebo SHAKE256 (rate 136), nasleduje PLShakeXOFAbsorb, PLShakeXOFFinalize a slučka PLShakeXOFSqueeze, ktorá pokračuje v permutácii pre ľubovoľnú dĺžku výstupu. Každá rutina rejection-sampling v ML-DSA jednotke je napísaná priamo proti tomuto štvorvolanovému API
Jeden engine, tri sady parametrov: TMLDSAParams
PDFlibPas popisuje celú sadu parametrov ML-DSA jedným záznamom a vyberá ju číslom sady, takže ML-DSA-44, 65 a 87 bežia cez rovnaké cesty kódu. Prvá fungujúca implementácia bola fixná 4x4 zostava natvrdo zapojená pre ML-DSA-44; zovšeobecnenie znamenalo zdvihnúť k a l, eta, tau, beta, gamma1 a gamma2, omega a dĺžku challenge do TMLDSAParams a odvodiť všetko ostatné. Verejné vstupné body sa stali trojriadkovými obalmi
Type
TMLDSAParams= Record
K, L, D, Eta, Tau, Beta, Gamma1, Gamma2, Omega: Integer;
Alpha, MW1: Cardinal;
W1BW, EtaBW, Gamma1BW, T1BW: Integer;
T0Rng: Cardinal;
CTildaBytes: Integer;
PublicKeyBytes, SecretKeyBytes, SignatureBytes: Integer;
End;
// Odvodené polia sa počítajú, nikdy neprepisujú z tabuľky
Params.Alpha:= 2* Cardinal(Params.Gamma2);
Params.MW1:= (Q- 1)div Params.Alpha;
Params.W1BW:= BitWidth(Params.MW1- 1);
Params.EtaBW:= BitWidth(2* Cardinal(Params.Eta));
Params.Gamma1BW:= BitWidth(Cardinal(Params.Gamma1));
Function MLDSA65Sign(Const SecretKey, Message, Context, Rnd: AnsiString;
Out Signature: AnsiString): Boolean;
Var
Params: TMLDSAParams;
Begin
BuildMLDSAParams(65, Params);
Result:= MLDSASignInternal(Params, SecretKey, Message, Context, Rnd,
Signature);
End;
Päť odvodených polí sa počíta zámerne, nie prepisuje z tabuliek FIPS 204. Ručne prepísané bitové šírky sú presne tou triedou konšt, ktorá vyzerá správne v revízii a je vedľa o jednu v produkcii, a dva zo skutočných defektov tejto portácie boli práve takéhoto tvaru. Deklarované veľkosti zostávajú ako pomenované konštanty pre validáciu: 1312 / 2560 / 2420 bajtov verejného kľúča, súkromného kľúča a podpisu pre ML-DSA-44, 1952 / 4032 / 3309 pre ML-DSA-65, 2592 / 4896 / 4627 pre ML-DSA-87
Kde sa portácia ML-DSA od nuly pokazí najskôr?
V expand_a, FIPS 204 algoritmus 32, a režim zlyhania je nádherně zavádzajúci. Matica A sa vzorkuje zasadením SHAKE128 s rho nasledovaným dvoma indexovými bajtmi, takže buffer semena má 34 bajtov: rho(32), potom j, potom i. Napísané v Pascale s indexovaním AnsiString od 1 sú tie dva bajty Msg[33] a Msg[34]. Prvý návrh tejto portácie ich napísal do Msg[34] a Msg[35], posunutý presne o jeden bajt, a výsledok bol pár kľúčov, kde rho zodpovedal testovaciemu vektoru perfektne, zatiaľ čo každý koeficient t bol zlý. Znečistená bola len matica a matica je tá jediná vec, ktorú verejný kľúč neprenáša doslova
Dva ďalšie defekty žili v tej istej rutine. Absorb dĺžka musí byť 34, nie 35; jeden navyše smetný bajt zmení celý stlačený stream. A vnútorná odmietacia slučka musí spotrebovať každú trojbajtovú skupinu, ktorú blok dodá, vrátane tej začínajúcej na ofsete 165 168-bajtového bloku SHAKE128, čo je 56 skupín na blok. Skript krížovej kontroly, ktorý sa zastavil na ofsete 162, zahodil chvost každého bloku a posunul vzorkovaný prefix t1 približne od trinásteho bajta ďalej
SetLength(Msg, 34);
Move(Rho[1], Msg[1], 32);
Msg[33]:= AnsiChar(J); // najprv index stĺpca
Msg[34]:= AnsiChar(I); // potom index riadku
PLShakeXOFInit(Ctx, True); // SHAKE128, rate 168
PLShakeXOFAbsorb(Ctx, @Msg[1], 34);
PLShakeXOFFinalize(Ctx);
Cnt:= 0;
While Cnt< N Do
Begin
PLShakeXOFSqueeze(Ctx, @Buf[0], 168);
BOff:= 0;
// BOff+2 <= 167 zachováva skupinu na ofsete 165: 56 trojíc na blok
While (BOff+ 2<= High(Buf))And (Cnt< N) Do
Begin
T3:= ((Buf[BOff+ 2]and $7F)shl 16)xor (Buf[BOff+ 1]shl 8)xor Buf[BOff];
If T3< Q Then
Begin
Poly^[Cnt]:= T3;
Inc(Cnt);
End;
Inc(BOff, 3);
End;
End;
S týmito troma opravami sa SHA-256 digesty kompletného verejného a súkromného kľúča ML-DSA-44 zhodovali so známymi vektormi FIPS 204. Jedna ladička lekcia stojí za pomenovanie, pretože stála sedenie: keď staviate Python krížovú kontrolu pre odmietaciu slučku riadenú XOF, hashlib.shake_128().digest(n) vracia rovnaký prefix pri každom volaní namiesto pokračovania streamu. Zoberte celú dĺžku raz a potom ju rozrežte na bloky veľkosti rate, inak vaša referencia rada znova skonzumuje presne tie hodnoty, ktoré váš Pascal správne odmietol
Vzorkovanie eta: prečo ML-DSA-65 potrebuje vlastnú vetvu
PDFlibPas drží dve samostatné cesty v expand_s, pretože FIPS 204 algoritmus 33 ich skutočne definuje dve. Pre eta = 2 sa každý nibble odmieta, keď dosiahne 15, a inak sa redukuje mod 5. Pre eta = 4 sa nibble odmieta pri 9 a vyššie a potom sa použije priamo bez akejkoľvek modulárnej redukcie. ML-DSA-65 je jediná dodávaná sada s eta = 4 a opätovné použitie cesty mod 5 pre ňu rozosunie s1 a s2 od prvého koeficientu, produkuje pár kľúčov, ktorý je interne konzistentný, verifikuje sa proti sebe samej a nezhoduje sa s ničím, čo produkujú ostatní
Procedure StoreNibble(Nibble: Byte);
Var
M: Integer;
Centered: Cardinal;
Begin
If Cnt>= N Then
Exit;
If Eta= 4 Then
Begin
If Nibble>= 9 Then // odmietnuť, potom vziať nibble tak, ako je
Exit;
M:= Nibble;
End
Else
Begin
If Nibble>= 15 Then // eta = 2: odmietnuť 15, potom redukovať mod 5
Exit;
M:= Nibble mod 5;
End;
If Eta>= M Then
Centered:= Eta- M
Else
Centered:= Q- (M- Eta);
Vec[I][Cnt]:= Centered;
Inc(Cnt);
End;
Veľkosti sú test: dĺžka c-tilde a bitová šírka gamma1
Dva parametre kódovania sa menia s bezpečnostnou úrovňou spôsobmi, ktoré sa ľahko prehliadnú, keď fungujúca zostava ML-DSA-44 stojí hneď vedľa. Challenge hash c-tilde má 2 x lambda / 8 bajtov, čo je 32 pre ML-DSA-44, 48 pre ML-DSA-65 a 64 pre ML-DSA-87. Nechať ho fixný na 32 dáva podpis ML-DSA-65 o 3293 bajtoch namiesto štandardných 3309 a KAT prefix diverguje okamžite. Pole záznamu CTildaBytes existuje presne preto, aby sa toto číslo nedalo zabudnúť
Druhý je pakovacia šírka maskového polynómu z. PDFlibPas ju počíta ako BitWidth(Gamma1), nie ako exponent: gamma1 = 2^19 pre ML-DSA-65 a 87 potrebuje 20 bitov na koeficient, nie 19, a ten jediný bit rozhoduje, či každý polynóm z zaberá 640 bajtov alebo niečo, čo žiadny verifikátor nerozparsuje. Verifikátor nosil zodpovedajúci defekt počas portácie, kde bol buffer deserializácie z veľkosti 192 bajtov namiesto 576. Dĺžka podpisu je najlacnejší regresný test, aký kedy napíšete: asertujte 2420, 3309 a 4627 proti Length(Signature) a väčšina chýb parametrovania sa ohlási skôr, než dosiahnete jediné kryptografické asertovanie
Podpisovanie bez neohraničenej slučky
Podpisovanie ML-DSA je založené na odmietaní, takže opakuje s inkrementovaným kappa, kým kandidátsky podpis prejde normou a kontrolou hint. PDFlibPas to obmedzuje explicitným externým rozpočtom 65535 pokusov; pri vyčerpaní MLDSASignInternal vracia False a nechá podpis prázdny namiesto krútenia vo vlákne produkujúcom dokumenty. V praxi oficiálny vektor ML-DSA-44 uspeje pri kappa = 4 s 55 hintmi proti stropu omega 80, takže rozpočet je bezpečnostné zábradlie, nie pracovný limit
Chyba, ktorá toto zábradlie urobila nutným, nebola číselná vôbec. Podpisovanie sa javilo zaseknuté, podozrenie padlo na decompose a make_hint (FIPS 204 algoritmy 36 a 39) a skutočná príčina bola obrátený akumulačný cieľ: vektor kŕmiaci výpočet hintu musí akumulovať c*t0, zatiaľ čo pôvodné c*t0 musí prežiť nedotknuté pre normu. Namierte oba do toho istého bufferu a slučka odmieta navždy s dokonale správnou aritmetikou. Na oboch cestách — úspechu aj vyčerpania rozpočtu — jednotka nuluje odvodené semená, súkromné polynómy, masky, challenge a kódovacie buffery; dodané volajúcim semeno, súkromný kľúč a rnd ostávajú zodpovednosťou volajúceho, čo je správne rozdelenie pre knižnicu, ktorá nemôže vedieť, odkiaľ tie reťazce prišli
Kde sa ML-DSA dnes stretáva s PDF podpisovým stackom?
Buďte presní v tom, čo existuje. PDFlibPas dodáva ML-DSA ako verifikované podpisové primitívy plus napojenie na mechanizmus PKCS #11, nie ako drop-in náhradu vášho súčasného PAdES výstupu. Tokenová cesta je TPDFlibPKCS11Client.SignMLDSA a je zámerne samostatným vstupným bodom, pretože CKM_ML_DSA konzumuje surovú správu namiesto predpočítaného digestu, takže existujúce SignHash a external-digest callbacky sa nedajú znova použiť. Objavovanie bez certifikátu vyžaduje explicitne zapnuté CertificateOptional spolu s labelom alebo ID súkromného kľúča a klient validuje CKA_PARAMETER_SET proti whieliste CKP_ML_DSA_44 / 65 / 87 v čase pripojenia, takže predvolené párovanie certifikátov RSA a ECDSA sa nikdy neomylom neuvoľní
Integrácia na úrovni dokumentov je časť ešte riadená štandardizačnou prácou, nie kódom knižnice. ISO 32000-2 §12.8 definuje podpisový slovník a jeho CMS payload a ISO/TS 32002 je vozidlo na rozšírenie tejto podpory o novšie hash a podpisové algoritmy; pokiaľ vaši validátori a partneri nenalejavia, klasické podpisovanie ostáva produkčnou cestou. Praktická postura sú paralelné koľaje: pokračujte v dodávaní podpisov PAdES B-B až B-LTA s timestampovaním a dátami dlhodobej validácie pre všetko, čo musí tretia strana dnes validovať, a vedľa toho dokazujte správu ML-DSA kľúčov a tokenovú integráciu. Pre lokálne experimenty dáva rovnaký workflow samopodpísaného certifikátu postavený na CryptoAPI podpisovú identitu bez zapojenia verejnej CA
Testujte zmenu sady parametrov tak, ako by ste testovali akúkoľvek inú zmenu podpisovania. Najprv veľkosti, potom oficiálne vektory, potom negatívne prípady: poškriabaný bajt podpisu, nezhodujúci sa kontextový reťazec, skrátený kľúč. PDFlibPas pokrýva všetky tie prípady v svojej DUnitX sade a rovnaká disciplína patrí aj do vášho vlastného pipeline, ideálne vedľa compliance a podpisovej workbench, ktorá dávkuje validáciu naprieč korpusom dokumentov, aby regresia nikdy nedosiahla zákazníka nepozorovane
Postkvantová pripravenosť pre dokumentový softvér nepríde ako jediný prepínač. Príde ako primitívy, ktoré môžete testovať, tokenová cesta, ktorú môžete napojiť, a štandardizačná koľaj, ktorú sledujete bez stávky aktuálneho vydania na nej. Ako sedí ML-DSA jednotka vedľa zvyšku podpisovania, šifrovania a nástrojov PDF/A v natívnom Object Pascal kódexu, ukazuje produktová stránka PDFlibPas Delphi PDF library, ktorá vypisuje kompletnú sadu komponentov a maticu podporovaných kompilátorov