PDFlibPas implementira ML-DSA, algoritam digitalnog potpisa zasnovan na modulskim rešetkama standardizovan u FIPS 204, u potpunosti u Object Pascal-u. Sva tri skupa parametara dolaze kao obične funkcije: MLDSA44Sign, MLDSA65Sign, MLDSA87Sign, plus odgovarajući KeyGen i Verify ulazi. Bez OpenSSL-a, bez DLL-a platforme, bez C lepa. Jedina jedinica PDFlibMLDSA ne zavisi ni od čega osim bibliotečke SHAKE sponge, i njen izlaz se poklapa sa zvaničnim FIPS 204 known-answer test vektorima bajt po bajt
Ta poslednja rečenica je jedini deo koji je zahtevao pravi posao. Pisanje aritmetike rešetki u Pascal-u je mehaničko; postići da se slaže sa NIST-om nije. Ono što sledi je inženjerski prikaz porta: kako su tri skupa parametara završila deleći jedan engine, i konkretne greške koje su razdvojile kompajlira se i pokreće od poklapa se sa KAT-om. Ako procenjujete post-kvantum opcije za Delphi ili C++Builder pipeline dokumenata, greške su koristan deo, jer svaka od njih proizvodi uverljiv izgledajući izlaz koji tiho pada u interoperabilnosti
Zašto pisati post-kvantum potpisivač u čistom Object Pascal-u?
Jer je alternativa jedna nativna zavisnost po cilju, a Delphi PDF biblioteka već ima dovoljno takvih. PDFlibPas se gradi preko Delphi, C++Builder i FPC/Lazarus na Win32, Win64 i Unix ciljevima; povezivanje C post-kvantum biblioteke značilo bi praćenje build-a nje za svaki od tih slotova, plus površinu pozivnih konvencija i vlasništva nad memorijom između njih. Čista Pascal jedinica se kompajlira gde god se kompajlira ostatak biblioteke, i to je ceo argument
ML-DSA ovo čini neobično jeftinim, jer mu je jedina primitivna zavisnost SHAKE. Nema big-integer sloja, nema eliptičke krive, nema odvojenog hash paketa. PDFlibPas je dobio streaming XOF u izdanju neposredno pre porta: TPLShakeXOF u PDFlibDigest, gde PLShakeXOFInit bira SHAKE128 (rate 168) ili SHAKE256 (rate 136), za čim slede PLShakeXOFAbsorb, PLShakeXOFFinalize i PLShakeXOFSqueeze petlja koja nastavlja permutovanje za proizvoljnu dužinu izlaza. Svaka rutina rejection uzorkovanja u ML-DSA jedinici pisana je direktno uz taj API od četiri poziva
Jedan engine, tri skupa parametara: TMLDSAParams
PDFlibPas opisuje ceo ML-DSA skup parametara jednim zapisom i bira ga po broju skupa, pa ML-DSA-44, 65 i 87 idu kroz iste kodne putanje. Prva radna implementacija bila je fiksni 4x4 build čvrsto vezan za ML-DSA-44; generalizacija je značila podizanje k i l, eta, tau, beta, gamma1 i gamma2, omega i dužine challenge-a u TMLDSAParams, pa izvođenje svega ostalog. Javni ulazni pointovi postali su omoti od tri linije
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 se računaju, nikad ne prepisuju iz tabele
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 se računaju, a ne prepisuju iz FIPS 204 tabela, namerno. Ručno prepisane bit širine su upravo klasa konstanti koja izgleda ispravno u pregledu i promaši za jedan u produkciji, i dve od pravih grešaka u ovom portu bile su tog oblika. Deklarisane veličine ostaju kao imenovane konstante radi validacije: 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
Gde ML-DSA port od nule prvo pogreši?
U expand_a, FIPS 204 Algoritmu 32, i oblik greške je prelepo obmanjujući. Matrica A se uzorkuje setovanjem SHAKE128 sa rho praćenim sa dva bajta indeksa, pa je bafer semena 34 bajta: rho(32), pa j, pa i. Napisano u Pascal-u sa AnsiString indeksiranjem od 1 ta dva bajta su Msg[33] i Msg[34]. Prva verzija porta upisala ih je u Msg[34] i Msg[35], pomerene za tačno jedan bajt, i rezultat je bio par ključeva čiji se rho savršeno poklapao sa test vektorom dok je svaki koeficijent t bio pogrešan. Samo je matrica bila zagađena, a matrica je upravo ono što javni ključ ne nosi doslovno
Još dve greške živele su u istoj rutini. Dužina absorb-a mora biti 34, ne 35; jedan višak otpadnog bajta menja ceo squeezed tok. A unutrašnja rejection petlja mora potrošiti svaku trobajtnu grupu koju blok može dati, uključujući onu koja počinje na ofsetu 165 bloka od 168 bajtova SHAKE128, što je 56 grupa po bloku. Skripta za unakrsnu proveru koja je stala na ofsetu 162 odbacila je rep svakog bloka i pomerila uzorkovani t1 prefiks od otprilike trinaestog bajta nadalje
SetLength(Msg, 34);
Move(Rho[1], Msg[1], 32);
Msg[33]:= AnsiChar(J); // prvo indeks kolone
Msg[34]:= AnsiChar(I); // pa indeks reda
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 zadržava 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;
Sa te tri ispravljene, SHA-256 sažeci kompletnog ML-DSA-44 javnog i tajnog ključa poklopili su se sa FIPS 204 known-answer vektorima. Jedna lekcija o debagovanju vredi da se imenuje, jer je koštala ceo session: kada pravite Python unakrsnu proveru za rejection petlju vođenu XOF-om, hashlib.shake_128().digest(n) vraća isti prefiks pri svakom pozivu umesto da nastavi tok. Uzmite celu dužinu jednom, pa je isecite u blokove veličine rate, ili će vaša referenca rado ponovo potrošiti upravo vrednosti koje je vaš Pascal ispravno odbio
Uzorkovanje eta: zašto ML-DSA-65 treba sopstvenu granu
PDFlibPas drži dve odvojene putanje u expand_s jer FIPS 204 Algoritam 33 stvarno definiše dve. Za eta = 2 svaki nibble se odbija kada dostigne 15, a inače redukuje mod 5. Za eta = 4 nibble se odbija na 9 i više, a zatim koristi direktno, bez ikakve modularne redukcije. ML-DSA-65 je jedini isporučeni skup sa eta = 4, i ponovna upotreba mod 5 putanje za njega izvitoperi s1 i s2 od prvog koeficijenta, proizvodeći par ključeva koji je interno dosledan, verifikuje se protiv samog sebe i ne poklapa se ni sa čime što drugi proizvode
Procedure StoreNibble(Nibble: Byte);
Var
M: Integer;
Centered: Cardinal;
Begin
If Cnt>= N Then
Exit;
If Eta= 4 Then
Begin
If Nibble>= 9 Then // odbij, pa uzmi nibble takav kakav jeste
Exit;
M:= Nibble;
End
Else
Begin
If Nibble>= 15 Then // eta = 2: odbij 15, pa redukuj 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: dužina c-tilde i bit širina gamma1
Dva parametra kodiranja variraju sa nivoom bezbednosti na načine koje je lako promašiti kada radni ML-DSA-44 build stoji odmah tu. Challenge hash c-tilde je 2 x lambda / 8 bajtova, što je 32 za ML-DSA-44, 48 za ML-DSA-65 i 64 za ML-DSA-87. Ako se ostavi fiksno na 32, dobija se ML-DSA-65 potpis od 3293 bajta umesto standardnih 3309, i KAT prefiks odmah divergira. Polje zapisa CTildaBytes postoji upravo da taj broj ne može biti zaboravljen
Drugi je širina pakovanja mask polinoma z. PDFlibPas je računa kao BitWidth(Gamma1), a ne kao eksponent: gamma1 = 2^19 za ML-DSA-65 i 87 traži 20 bitova po koeficijentu, ne 19, i taj jedan bit odlučuje da li svaki z polinom zauzima 640 bajtova ili nešto što nijedan verifikator neće parsirati. Verifikator je nosio odgovarajuću grešku tokom porta, gde je z deserializacijski bafer bio 192 bajta umesto 576. Dužina potpisa je najjeftiniji regresioni test koji ćete ikada napisati: asertirajte 2420, 3309 i 4627 protiv Length(Signature) i većina grešaka parametrizacije sama se prijavi pre nego što stignete do ijedne kriptografske asercije
Potpisivanje bez petlje bez granica
ML-DSA potpisivanje je zasnovano na odbijanju, pa ponavlja sa uvećanim kappa dok kandidat potpis ne prođe provere norme i hint-ova. PDFlibPas to ograničava eksplicitnim spoljnim budžetom od 65535 pokušaja; pri iscrpljivanju MLDSASignInternal vraća False i ostavlja potpis prazan umesto da se vrti unutar niti za proizvodnju dokumenata. U praksi zvanični ML-DSA-44 vektor uspeva na kappa = 4 sa 55 hint-ova prema omega plafonu od 80, pa je budžet sigurnosna ograda, a ne radna granica
Greška zbog koje je ta ograda delovala neophodnom nije bila numerička uopšte. Potpisivanje je delovalo zaglavljeno, 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 hint-a mora akumulirati c*t0, dok originalni c*t0 mora preživeti netaknut za proveru norme. Naciljajte oba na isti bafer i petlja odbija zauvek uz savršeno ispravnu aritmetiku. Na putanjama uspeha i iscrpljenog budžeta jedinica nulira izvedena semena, tajne polinome, maske, challenge i bafer kodiranja; seme, tajni ključ i rnd koje je dostavio pozivaoc ostaju odgovornost pozivaoca, što je prava podela za biblioteku koja ne može znati odakle ti stringovi potiču
Gde se ML-DSA danas susreće sa PDF stekom potpisa?
Budite precizni oko toga šta postoji. PDFlibPas isporučuje ML-DSA kao verifikovane potpisne primitive plus PKCS #11 povezivanje mehanizma, ne kao drop-in zamenu za vaš trenutni PAdES izlaz. Putanja tokena je TPDFlibPKCS11Client.SignMLDSA, i namerno je odvojeni ulazni point jer CKM_ML_DSA troši sirovu poruku, a ne prethodno izračunat sažetak, pa postojeći SignHash i external-digest callbacks ne mogu biti iskorišćeni. Otkrivanje bez sertifikata traži da CertificateOptional bude eksplicitno uključen uz labelu ili ID privatnog ključa, i klijent validira CKA_PARAMETER_SET protiv CKP_ML_DSA_44 / 65 / 87 bele liste u trenutku povezivanja, pa se podrazumevano RSA i ECDSA uparivanje sertifikata nikad ne olakša slučajno
Integracija na nivou dokumenta je deo koji još uvek upravlja standardizacija, a ne bibliotečki kod. ISO 32000-2 §12.8 definiše signature rečnik i njegov CMS sadržaj, a ISO/TS 32002 je vozilo za proširenje te podrške na novije hash i potpisne algoritme; dok se vaši validatori i partneri ne priključe, klasično potpisivanje ostaje produkciona putanja. Praktičan stav su paralelne staze: nastavite isporučivati PAdES B-B do B-LTA potpise sa timestamping-om i podacima dugotrajne validacije za sve što treća strana mora validirati danas, dok uz to dokazujete ML-DSA rukovanje ključevima i integraciju tokena. Za lokalne eksperimente isti tok samopotpisanog sertifikata izgrađen na CryptoAPI vam daje potpisni identitet bez uključivanja javnog CA-ja
Testirajte promenu skupa parametara onako kako biste testirali bilo koju drugu promenu potpisivanja. Prvo veličine, pa zvanični vektori, pa negativni slučajevi: manipulisani bajt potpisa, nepoklapajući context string, skraćeni ključ. PDFlibPas pokriva sve to u svojoj DUnitX suite, i ista disciplina pripada vašem pipeline-u, idealno uz compliance i signing workbench koji grupiše validaciju preko korpusa dokumenata pa regresija nikad ne stigne do klijenta neprimećena
Post-kvantum spremnost za softver dokumenata neće stići kao jedan prekidač. Stići će kao primitive koje možete testirati, putanja tokena koju možete povezati i standardizaciona staza koju pratite bez kladenja trenutnog izdanja na nju. Da vidite kako ML-DSA jedinica stoji uz ostatak signing, encryption i PDF/A alata u nativnoj Object Pascal bazi koda, PDFlibPas Delphi PDF library product page navodi kompletan skup komponenti i matricu podržanih kompajlera