A PDFlibPas az ML-DSA-t, a FIPS 204-ben szabványosított Module-Lattice-Based Digital Signature Algorithm-t teljes egészében Object Pascalban implementálja. Mindhárom paraméterhalmaz közönséges függvényként érkezik: MLDSA44Sign, MLDSA65Sign, MLDSA87Sign, a hozzájuk illő KeyGen és Verify belépőkkel. Nincs OpenSSL, nincs platform-DLL, nincs C-ragasztó. Az egyetlen PDFlibMLDSA egység semmire nem támaszkodik, csak a könyvtár SHAKE szivacsára, és a kimenete bájtról bájtra egyezik a hivatalos FIPS 204 known-answer tesztvektorokkal
Az utolsó mondat az egyetlen rész, amely igazi munkába került. Rácsaritmetika írása Pascalban mechanikus; azt elérni, hogy egyezzen a NIST-tel, nem az. A következők a portolás mérnöki beszámolója: hogyan jutottak el a három paraméterhalmaz egyetlen közös motorhoz, és milyen konkrét hibák választották el a lefordul és fut állapotot az egyezik a KAT-tal állapottól. Ha Delphi vagy C++Builder dokumentumfolyamhoz mérlegel post-kvantum opciókat, a hibák a hasznos rész, mert mindegyik hihetőnek tűnő, de interoperabilitásban csendesen elbukó kimenetet termel
Miért érdemes post-kvantum aláírót tiszta Object Pascalban írni?
Mert az alternatíva célpontranként egy natív függőség, és egy Delphi PDF-könyvtárnak már így is elég függősége van. A PDFlibPas Delphi, C++Builder és FPC/Lazarus felett fordul Win32, Win64 és Unix célpontokon; egy C post-kvantum könyvtár bekötése azt jelentené, hogy minden ilyen helyhez követni kell a buildjét, ráadásul a hívási konvenció és a memória-tulajdonlás felületét is köztük. A tiszta Pascal egység ott fordul, ahol a könyvtár többi része is, és ez a teljes érv
Az ML-DSA ezt szokatlanul olcsóvá teszi, mert egyetlen primitív függősége a SHAKE. Nincs nagyegész réteg, nincs elliptikus görbe, nincs külön hash-készlet. A PDFlibPas a portolást közvetlenül megelőző kiadásban kapott streaming XOF-ot: a TPLShakeXOF-ot a PDFlibDigest-ben, ahol a PLShakeXOFInit SHAKE128-at (rate 168) vagy SHAKE256-ot (rate 136) választ, majd PLShakeXOFAbsorb, PLShakeXOFFinalize és egy PLShakeXOFSqueeze ciklus következik, amely tetszőleges kimenethosszig permutál tovább. Az ML-DSA egység minden elutasításos mintavételi rutinja közvetlenül erre a négy hívásos API-ra épül
Egy motor, három paraméterhalmaz: TMLDSAParams
A PDFlibPas egyetlen rekorddal írja le a teljes ML-DSA paraméterhalmazt, és halmazszám szerint választja ki, így az ML-DSA-44, 65 és 87 ugyanazokon a kódutakon fut. Az első működő implementáció fix 4x4-es build volt, ML-DSA-44-re bedrótozva; általánosítása azt jelentette, hogy a k és l, eta, tau, beta, gamma1 és gamma2, omega és a kihívás hossza felkerült a TMLDSAParams-ba, majd minden más levezetésre került. A publikus belépőpontok három soros csomagolókká váltak
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;
// A levezetett mezők számoltak, sosem táblából átírtak
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;
Az öt levezetett mező szándékosan számolva van, nem a FIPS 204 tábláiból másolva. A kézzel átírt bitszélességek pontosan azok a konstansosztály, amelyek a review-ban jól néznek ki, a produkciban pedig eggyel elcsúsznak, és a portolás valódi hibáinak kettő is ilyen alakú volt. A deklarált méretek nevesített konstansként maradnak a validációhoz: 1312 / 2560 / 2420 bájt nyilvános kulcs, titkos kulcs és aláírás az ML-DSA-44-hez, 1952 / 4032 / 3309 az ML-DSA-65-höz, 2592 / 4896 / 4627 az ML-DSA-87-hez
Hol hibázik először egy nulláról írt ML-DSA portolás?
A expand_a-ban, a FIPS 204 32-es algoritmusában, és a hibamódus gyönyörűen megtévesztő. Az A mátrixot úgy veszik mintavételre, hogy a SHAKE128-at a rho-val és utána két indexbájttal vetik be, így a vetőpuffer 34 bájt: rho(32), aztán j, aztán i. Pascalban, 1-alapú AnsiString indexeléssel írva ez a két bájt a Msg[33] és a Msg[34]. A portolás első vázlata a Msg[34]-be és a Msg[35]-be írta őket, pontosan egy bájttal eltolva, és az eredmény egy kulcspár lett, amelynek rho-ja tökéletesen egyezett a tesztvektorral, miközben a t minden koefficiense rossz volt. Csak a mátrix szennyeződött, és a mátrix az egyetlen dolog, amelyet a nyilvános kulcs nem hordoz szó szerint
Két további hiba élt ugyanabban a rutinban. Az absorb hossznak 34-nek kell lennie, nem 35; egy felesleges szemétbájt az egész kinyomott streamet megváltoztatja. És a belső elutasítási ciklusnak minden hárombájtos csoportot fel kell emésztenie, amelyet a blokk adni tud, ideértve a 168 bájtos SHAKE128 blokk 165-ös offszetjén indulót is, ami blokkonként 56 csoport. A 162-es offszetnél megálló ellenőrző szkript minden blokk végét eldobta, és a mintavételezett t1 prefixet nagyjából a tizenharmadik bájttól kezdve eltolta
SetLength(Msg, 34);
Move(Rho[1], Msg[1], 32);
Msg[33]:= AnsiChar(J); // előbb az oszlopindex
Msg[34]:= AnsiChar(I); // aztán a sorindex
PLShakeXOFInit(Ctx, True); // SHAKE128, 168-os rate
PLShakeXOFAbsorb(Ctx, @Msg[1], 34);
PLShakeXOFFinalize(Ctx);
Cnt:= 0;
While Cnt< N Do
Begin
PLShakeXOFSqueeze(Ctx, @Buf[0], 168);
BOff:= 0;
// a BOff+2 <= 167 megtartja a 165-ös offszetű csoportot: blokkonként 56 tripla
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;
A három kijavításával az ML-DSA-44 teljes nyilvános és titkos kulcsának SHA-256 digesztjei egyeztek a FIPS 204 known-answer vektorokkal. Egy hibakeresési tanulság is megérdemli a megnevezést, mert egy munkamenetbe került: amikor Pythonos ellenőrzést épít egy XOF-vezérelt elutasítási ciklushoz, a hashlib.shake_128().digest(n) minden hívásnál ugyanazt a prefixet adja vissza, a stream folytatása helyett. Vegye fel egyszer az egész hosszt, majd szeletelje rate-méretű blokkokra, különben a referenciája boldogan újból elfogyasztja pontosan azokat az értékeket, amelyeket a Pascalja helyesen elutasított
Eta mintavétel: miért kell az ML-DSA-65-nek saját ága
A PDFlibPas két külön utat tart a expand_s-ben, mert a FIPS 204 33-as algoritmusa valóban kettőt definiál. Eta = 2 esetén minden nibble 15 elérésekor elutasításra kerül, egyébként mod 5-re redukálódik. Eta = 4 esetén a nibble 9-nél vagy afelett utasítódik el, majd közvetlenül kerül felhasználásra, semmilyen moduláris redukció nélkül. Az ML-DSA-65 az egyetlen kiszállított halmaz eta = 4-gyel, és a mod 5 út újrahasznosítása rajta s1-et és s2-t már az első koefficienstől elcsúsztatja, olyan kulcspárt termelve, amely belsőleg konzisztens, önmagával ellenőrizhető, és semmi olyanhoz nem egyezik, amit bárki más termel
Procedure StoreNibble(Nibble: Byte);
Var
M: Integer;
Centered: Cardinal;
Begin
If Cnt>= N Then
Exit;
If Eta= 4 Then
Begin
If Nibble>= 9 Then // elutasítás, aztán a nibble úgy, ahogy van
Exit;
M:= Nibble;
End
Else
Begin
If Nibble>= 15 Then // eta = 2: a 15 elutasítása, aztán mod 5 redukció
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;
A méretek a teszt: c-tilde hossz és a gamma1 bitszélesség
Két kódolási paraméter a biztonsági szinttel együtt változik olyan módokon, amelyek könnyen kicsúsznak a figyelem alól, amikor egy működő ML-DSA-44 build éppen ott áll. A kihívás hash, a c-tilde, 2 x lambda / 8 bájt, ami ML-DSA-44 esetén 32, ML-DSA-65 esetén 48, ML-DSA-87 esetén 64. 32-n rögzítve hagyva 3293 bájtos ML-DSA-65 aláírás születik a szabványos 3309 helyett, és a KAT prefix azonnal széttér. A rekord CTildaBytes mezője pontosan azért létezik, hogy ez a szám ne felejthető el
A második a z maszkpolinom csomagolási szélessége. A PDFlibPas BitWidth(Gamma1)-ként számolja, nem a kitevőként: gamma1 = 2^19 az ML-DSA-65 és 87 esetén koefficiensenként 20 bitet igényel, nem 19-et, és ez az egyetlen bit dönti el, hogy minden z polinom 640 bájtot foglal-e, vagy olyasmit, amelyet egyetlen ellenőrző sem fog értelmezni. Az ellenőrző a portolás alatt magán hordozta a páros hibát: a z deszerializációs pufferje 192 bájtra volt méretezve 576 helyett. Az aláíráshossz a legolcsóbb regressziós teszt, amit valaha ír: állítsa 2420, 3309 és 4627 ellen a Length(Signature)-et, és a legtöbb paraméterezési hiba magától bejelentkezik, még mielőtt egyetlen kriptográfiai állításhoz érne
Aláírás korlátlan ciklus nélkül
Az ML-DSA aláírás elutasításos, ezért növelt kappa-val próbálkozik újra, amíg egy jelölt aláírás át nem megy a normán és a hint ellenőrzéseken. A PDFlibPas ezt explicit, 65535 kísérletes külső kerettel szorítja korlát közé; a kimerüléskor a MLDSASignInternal False-t ad vissza, és üresen hagyja az aláírást, ahelyett hogy egy dokumentum-előállító szálon pörögne. A gyakorlatban a hivatalos ML-DSA-44 vektor kappa = 4-nél sikeres, 55 hinttel az omega 80-as plafonjával szemben, így a keret biztonsági korlát, nem működő határ
A hiba, amely azt éreztette, hogy ez a korlát kell, nem volt numerikus egyáltalán. Az aláírás akadásnak tűnt, a gyanú a decompose-ra és a make_hint-re terelődött (FIPS 204 36-os és 39-es algoritmusai), a valódi ok pedig megfordított akkumulációs cél volt: a hint-számítást tápláló vektornak a c*t0-t kell akkumulálnia, miközben az eredeti c*t0-nak érintetlenül kell túlélnie a normatesztet. Mindkettőt ugyanabba a pufferbe irányítva a ciklus örökké utasít el, tökéletesen helyes aritmetikával. A sikeres és a keret-kimerülési útvonalon egyaránt az egység nullázza a levezetett vetőmagokat, titkos polinomokat, maszkokat, a kihívást és a kódolási puffereket; a hívó által szolgáltatott vetőmag, titkos kulcs és rnd a hívó felelőssége marad, ami a helyes felosztás egy olyan könyvtárban, amely nem tudhatja, honnan származnak azok a sztringek
Hol találkozik az ML-DSA ma a PDF aláírási veremmel?
Legyen pontos abban, ami létezik. A PDFlibPas az ML-DSA-t ellenőrzött aláírási primitívekként szállítja, mellé egy PKCS #11 mechanizmus-kötést, nem pedig az Ön jelenlegi PAdES kimenetének bedobozos helyettesítőjeként. A token-útvonal a TPDFlibPKCS11Client.SignMLDSA, és tudatosan külön belépőpont, mert a CKM_ML_DSA a nyers üzenetet fogyasztja, nem előre számolt digesztet, így a meglévő SignHash és külső digeszt-visszahívások nem használhatók újra. A tanúsítvány nélküli felderítés megköveteli a CertificateOptional explicit engedélyezését, privát kulcs címkével vagy azonosítóval együtt, és a kliens a CKA_PARAMETER_SET-et a CKP_ML_DSA_44 / 65 / 87 fehérlistával validálja csatlakozáskor, így az alapértelmezett RSA és ECDSA tanúsítvány-párosítás véletlenül sosem lazul fel
A dokumentumszintű integráció az a rész, amelyet még a szabványosítási munka irányít, nem a könyvtár kódja. Az ISO 32000-2 §12.8 definiálja az aláírási szótárt és annak CMS tartalmát, az ISO/TS 32002 pedig az a jármű, amely ezt a támogatást újabb hash- és aláírási algoritmusokra terjeszti ki; amíg az Ön validátorai és partnerei nem követik, a klasszikus aláírás marad a produkciós útvonal. A gyakorlati tartás párhuzamos sínek: továbbra is szállítson PAdES B-B-től B-LTÁ-ig terjedő aláírásokat időbélyegzéssel és hosszú távú validációs adatokkal mindenhez, amelyet ma egy harmadik félnek validálnia kell, miközben mellette bizonyítja ki az ML-DSA kulcskezelést és token-integrációt. Helyi kísérletekhez ugyanez a CryptoAPI-ra épített önaláírt tanúsítvány-munkafolyamat aláíró identitást ad nyilvános CA bevonása nélkül
A paraméterhalmaz-változást úgy tesztelje, ahogy bármilyen más aláírási változást tesztelne. Először a méretek, aztán a hivatalos vektorok, aztán a negatív esetek: egy megbabonált aláírásbájt, egy nem illő kontextus-sztring, egy csonka kulcs. A PDFlibPas mindezt lefedi a DUnitX készletében, és ugyanez a fegyelem való az Ön saját folyamatába is, ideálisan a megfelelőségi és aláíró munkapad mellett, amely dokumentumkorpuszon kötegelve validál, hogy egy regresszió sosem érjen észrevétlenül ügyfélhez
A post-kvantum készenlét a dokumentumszoftverben nem egyetlen kapcsolóként fog megérkezni. Megérkezik tesztelhető primitívekként, beköthető token-útvonalként, és egy szabványos nyomvonalasként, amelyet követni lehet anélkül, hogy az aktuális kiadást rá feltétbe tenné. Hogy az ML-DSA egység hogyan ül a többi aláírási, titkosítási és PDF/A eszköz mellett egy natív Object Pascal kódbázisban, azt a PDFlibPas Delphi PDF-könyvtár termékoldala mutatja, a teljes komponenskészlettel és a támogatott fordító-mátrixszal