PDFlibPas implementira ML-DSA, Module-Lattice-Based Digital Signature Algorithm standardiziran u FIPS 204, u cijelosti u Object Pascalu. Sva tri skupa parametara stižu kao obične funkcije: MLDSA44Sign, MLDSA65Sign, MLDSA87Sign, plus odgovarajući KeyGen i Verify ulazi. Bez OpenSSL-a, bez platformskog DLL-a, bez C ljepila. Jedina jedinica PDFlibMLDSA ne ovisi ni o čemu osim o knjižničnoj SHAKE spužvi, i njen izlaz poklapa se sa službenim FIPS 204 vektorima poznatih odgovora bajt po bajt
Ta zadnja rečenica jedini je dio koji je zahtijevao pravi rad. Pisati rešetkastu aritmetiku u Pascalu mehanički je posao; natjerati je da se slaže s NIST-om nije. Ono što slijedi inženjerski je izvještaj o portanju: kako su tri skupa parametara završila dijeleći jedan stroj i koje su specifične pogreške razdvajale prevodi se i izvodi od slaže se s KAT-om. Ako procjenjujete postkvantne opcije za cjevovod dokumenata u Delphiju ili C++Builderu, pogreške su korisni dio, jer svaka od njih proizvodi uvjerljiv izlaz koji tiho pada u međusobnoj suradljivosti
Zašto pisati postkvantni potpisivač u čistom Object Pascalu?
Jer je alternativa jedna nativna ovisnost po ciljnoj platformi, a Delphi PDF knjižnica već ima dovoljno takvih. PDFlibPas se gradi na Delphiju, C++Builderu i FPC/Lazarusu na Win32, Win64 i Unix ciljevima; vezati C postkvantnu knjižnicu značilo bi pratiti njezin build za svaki od tih slotova, plus površinu pozivnih konvencija i vlasništva nad memorijom među njima. Čista Pascal jedinica prevodi se gdjegod se prevodi ostatak knjižnice, i to je cijeli argument
ML-DSA to čini neobično jeftinim, jer mu je jedina primitivna ovisnost SHAKE. Nema sloja velikih brojeva, nema eliptičke krivulje, nema zasebnog skupa hasheva. PDFlibPas je dobio streaming XOF u izdanju neposredno prije portanja: TPLShakeXOF u PDFlibDigest, gdje PLShakeXOFInit odabire SHAKE128 (rate 168) ili SHAKE256 (rate 136), nakon čega slijede PLShakeXOFAbsorb, PLShakeXOFFinalize i PLShakeXOFSqueeze petlja koja neprestano permutira za proizvoljnu duljinu izlaza. Svaka rutina uzorkovanja s odbacivanjem u ML-DSA jedinici napisana je izravno uz taj API od četiri poziva
Jedan stroj, tri skupa parametara: TMLDSAParams
PDFlibPas opisuje cijeli ML-DSA skup parametara jednim zapisom i bira ga po broju skupa, pa ML-DSA-44, 65 i 87 prolaze istim stazama koda. Prva radna implementacija bila je fiksni 4x4 build žičan za ML-DSA-44; generalizirati je značilo podići k i l, eta, tau, beta, gamma1 i gamma2, omega i duljinu izazova u TMLDSAParams, pa izvesti sve ostalo. Javni ulazni bodovi postali su omoti od tri retka
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;
// Izvedena polja računaju se, nikad se ne prepisuju iz tablice
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;
Pet izvedenih polja računa se namjerno, umjesto da se iskopira iz FIPS 204 tablica. Ručno prepisane širine bitova upravo su ona vrsta konstanti koja u pregledu izgleda ispravno, a u proizvodnji odstupi za jedan, i dvije od stvarnih pogrešaka u ovom portanju bile su takvog oblika. Deklarirane veličine ostaju kao imenovane konstante radi provjere: 1312 / 2560 / 2420 bajtova javnog ključa, tajnog ključa i potpisa za ML-DSA-44, 1952 / 4032 / 3309 za ML-DSA-65, 2592 / 4896 / 4627 za ML-DSA-87
Gdje se portanje ML-DSA od nule prvo pokvari?
U expand_a, FIPS 204 Algoritam 32, i oblik otkaza lijepo je obmanjujući. Matrica A uzorkuje se sjemenjenjem SHAKE128 s rho iza kojega slijede dva indeksna bajta, pa je međuspremnik sjemena 34 bajta: rho(32), zatim j, zatim i. Napisano u Pascalu s AnsiString indeksiranjem od 1 ta su dva bajta Msg[33] i Msg[34]. Prvi nacrt ovog portanja napisao ih je u Msg[34] i Msg[35], pomaknuto točno jedan bajt, i rezultat je bio par ključeva čiji se rho savršeno poklapao s testnim vektorom dok je svaki koeficijent t bio pogrešan. Zagađena je bila samo matrica, a matrica je upravo ono što javni ključ ne nosi doslovno
Još dvije pogreške živjele su u istoj rutini. Absorb duljina mora biti 34, a ne 35; jedan dodatni otpadni bajt mijenja cijeli istisnuti tok. A unutarnja petlja odbacivanja mora potrošiti svaku trobajtnu grupu koju blok može ponuditi, uključujući onu koja počinje na ofsetu 168-bajtnog SHAKE128 bloka od 165, što je 56 grupa po bloku. Skripta unakrsne provjere koja je stala na ofsetu 162 odbacila je rep svakog bloka i pomaknula uzorkovani t1 prefiks od otprilike trinaestog bajta nadalje
SetLength(Msg, 34);
Move(Rho[1], Msg[1], 32);
Msg[33]:= AnsiChar(J); // najprije indeks stupca
Msg[34]:= AnsiChar(I); // zatim indeks retka
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 čuva grupu na ofsetu 165: 56 trojki po bloku
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 ta tri ispravljena, SHA-256 sažeci potpunih ML-DSA-44 javnih i tajnih ključeva poklapali su se s FIPS 204 vektorima poznatih odgovora. Jedna lekcija ispravljanja vrijedi i imenovanja, jer je koštala cijelu sesiju: kad gradite Python unakrsnu provjeru za petlju odbacivanja koju vodi XOF, hashlib.shake_128().digest(n) vraća isti prefiks pri svakom pozivu umjesto da nastavi tok. Uzmite cijelu duljinu jednom, pa je isjecite u blokove veličine rate, ili će vaša referenca rado ponovno potrošiti upravo one vrijednosti koje je vaš Pascal ispravno odbacio
Uzorkovanje ete: zašto ML-DSA-65 treba svoju granu
PDFlibPas drži dva odvojena puta u expand_s jer FIPS 204 Algoritam 33 stvarno definira dva. Za eta = 2 svaki se nibble odbacuje kad dođe do 15, a inače se smanjuje mod 5. Za eta = 4 nibble se odbacuje na 9 i više, a zatim se koristi izravno, bez ikakvog modularnog smanjenja. ML-DSA-65 jedini je isporučeni skup s eta = 4, i ponovna upotreba mod 5 puta za njega poravnava s1 i s2 pogrešno od prvog koeficijenta, proizvodeći par ključeva koji je unutarnje dosljedan, verificira se protiv samog sebe i ne poklapa se s ničim što itko drugi proizvodi
Procedure StoreNibble(Nibble: Byte);
Var
M: Integer;
Centered: Cardinal;
Begin
If Cnt>= N Then
Exit;
If Eta= 4 Then
Begin
If Nibble>= 9 Then // odbaci, pa uzmi nibble takvog kakav je
Exit;
M:= Nibble;
End
Else
Begin
If Nibble>= 15 Then // eta = 2: odbaci 15, pa smanji 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;
Veličine su test: duljina c-tilde i širina bitova gamma1
Dva parametra kodiranja mijenjaju se sa sigurnosnom razinom na načine koje je lako promašiti kad radni ML-DSA-44 build sjedi odmah do. Izazovni hash c-tilde jest 2 x lambda / 8 bajtova, što je 32 za ML-DSA-44, 48 za ML-DSA-65 i 64 za ML-DSA-87. Ostaviti ga fiksiranim na 32 daje ML-DSA-65 potpis od 3293 bajta umjesto standardnih 3309, i KAT prefiks odmah divergira. Polje zapisa CTildaBytes postoji upravo da se taj broj ne može zaboraviti
Drugi je širina pakiranja maskirnog polinoma z. PDFlibPas je računa kao BitWidth(Gamma1), a ne kao eksponent: gamma1 = 2^19 za ML-DSA-65 i 87 treba 20 bitova po koeficijentu, a ne 19, i taj jedan bit odlučuje zauzima li svaki z polinom 640 bajtova ili nešto što nijedan verificiratelj neće parsirati. Verificiratelj je tijekom portanja nosio odgovarajuću pogrešku, gdje je z međuspremnik deserializacije bio veličine 192 bajta umjesto 576. Duljina potpisa najjeftiniji je regresijski test koji ćete ikad napisati: tvrdite 2420, 3309 i 4627 protiv Length(Signature) i većina pogrešaka parametrizacije sama se najavi prije nego što dođete do ijedne kriptografske tvrdnje
Potpisivanje bez petlje bez granica
ML-DSA potpisivanje temelji se na odbacivanju, pa pokušava ponovno s uvećanim kappa dok kandidat potpisa ne prođe provjere norme i hintova. PDFlibPas to ograničava izričitim vanjskim budžetom od 65535 pokušaja; pri iscrpljivanju MLDSASignInternal vraća False i ostavlja potpis praznim umjesto da se vrti unutar niti proizvodnje dokumenata. U praksi službeni ML-DSA-44 vektor uspijeva na kappa = 4 s 55 hintova protiv omega stropa od 80, pa je budžet sigurnosna ograda, a ne radna granica
Pogreška zbog koje je ta ograda djelovala nužnom nije bila brojčana uopće. Potpisivanje se činilo zaglavljenim, sumnja je pala na decompose i make_hint (FIPS 204 Algoritmi 36 i 39), a pravi uzrok bio je obrnut cilj akumulacije: vektor koji hrani računanje hintova mora akumulirati c*t0, dok izvorni c*t0 mora preživjeti netaknut radi provjere norme. Ciljajte oba u isti međuspremnik i petlja odbacuje zauvijek uz savršeno ispravnu aritmetiku. Na putu uspjeha i na putu iscrpljenog budžeta jedinica nulira izvedena sjemena, tajne polinome, maske, izazov i međuspremnike kodiranja; sjeme, tajni ključ i rnd koje daje pozivatelj ostaju odgovornost pozivatelja, što je prava podjela za knjižnicu koja ne može znati odakle ti nizovi dolaze
Gdje se ML-DSA danas susreće sa stogom PDF potpisa?
Budite precizni oko onoga što postoji. PDFlibPas isporučuje ML-DSA kao verificirane potpisne primitive plus PKCS #11 vezanje mehanizma, a ne kao drop-in zamjenu za vaš trenutačni PAdES izlaz. Put tokena jest TPDFlibPKCS11Client.SignMLDSA, i namjerno je zasebni ulazni bod jer CKM_ML_DSA troši sirovu poruku umjesto unaprijed izračunatog sažetka, pa postojeći SignHash i vanjsko-sažetni povratni pozivi ne mogu se ponovno upotrijebiti. Otkrivanje bez certifikata traži izričito omogućen CertificateOptional uz oznaku ili ID privatnog ključa, i klijent provjerava CKA_PARAMETER_SET protiv bijelog popisa CKP_ML_DSA_44 / 65 / 87 u trenutku povezivanja, pa se zadani RSA i ECDSA par certifikata nikad ne opusti slučajno
Integracija na razini dokumenta još je dio kojim upravlja standardizacijski rad, a ne kod knjižnice. ISO 32000-2 §12.8 definira rječnik potpisa i njegov CMS sadržaj, a ISO/TS 32002 vozilo je za proširenje te podrške na novije hash i potpisne algoritme; dok vam se verificiratelji i protustrane ne pridruže, klasično potpisivanje ostaje produkcijski put. Praktičan stav su paralelni kolosijeci: nastavite isporučivati PAdES B-B do B-LTA potpise s vremenskim žigom i podacima dugotrajne validacije za sve što treća strana danas mora verificirati, dok uz to dokazujete ML-DSA rukovanje ključevima i integraciju tokena. Za lokalne pokuse isti tijek samopotpisanih certifikata izgrađen na CryptoAPI-ju daje vam potpisni identitet bez uključivanja javnog CA-a
Promjenu skupa parametara testirajte onako kako biste testirali svaku drugu potpisnu promjenu. Najprije veličine, zatim službeni vektori, pa negativni slučajevi: dopisani bajt potpisa, neusklađeni kontekstni niz, obrezan ključ. PDFlibPas pokriva sve to u svom DUnitX skupu, i ista disciplina pripada vašem cjevovodu, idealno uz radni stol usklađenosti i potpisivanja koji grupira validaciju preko korpusa dokumenata da regresija nikad ne stigne do kupca nezapaženo
Postkvantna spremnost za softver dokumenata neće doći kao jedan prekidač. Dolazi kao primitive koje možete testirati, put tokena koji možete povezati i standardizacijska staza koju pratite bez klađenja trenutačnog izdanja na nju. Da vidite kako ML-DSA jedinica stoji uz ostatak alata za potpisivanje, šifriranje i PDF/A u nativnoj Object Pascal bazi koda, stranica proizvoda PDFlibPas Delphi PDF library navodi potpuni skup komponenata i matricu podržanih prevodilaca