PDFlibPas, FIPS 204'te standartlaştırılan Module-Lattice-Based Digital Signature Algorithm olan ML-DSA'yı tamamen Object Pascal ile uygular. Üç parametre kümesinin tamamı sıradan fonksiyonlar olarak gelir: MLDSA44Sign, MLDSA65Sign, MLDSA87Sign ve eşleşen KeyGen ile Verify girişleri. OpenSSL yok, platform DLL'i yok, C tutkalı yok. Tek unit olan PDFlibMLDSA, yalnızca kütüphanenin SHAKE süngesine bağlıdır ve çıktısı resmi FIPS 204 known-answer test vektörleriyle bayt bayt eşleşir
Son cümle, gerçek emek isteyen tek kısımdır. Örgü (lattice) aritmetiğini Pascal'da yazmak mekaniktir; onu NIST ile hem fikir etmek değildir. Aşağıdaki, portun mühendislik anlatımıdır: üç parametre kümesinin nasıl tek motoru paylaşmaya vardığı ve derlenir ve çalışır ile KAT ile eşleşir arasına mesafe koyan somut kusurlar. Delphi ya da C++Builder belge hattı için kuantum sonrası seçenekleri değerlendiriyorsanız, faydalı kısım kusurlardır; çünkü her biri, birlikte çalışabilirliği sessizce bozan, inandırıcı görünümlü çıktı üretir
Kuantum sonrası imzalayıcı neden saf Object Pascal ile yazılmalı?
Çünkü alternatif hedef başına bir yerel bağımlılıktır ve bir Delphi PDF kütüphanesinin bundan zaten yeterince fazlası vardır. PDFlibPas, Win32, Win64 ve Unix hedeflerinde Delphi, C++Builder ve FPC/Lazarus üzerinde inşa edilir; bir C kuantum sonrası kütüphaneye bağlanmak, o her yuva için ayrı bir derlemeyi takip etmek demek olur; ayrıca aralarındaki çağrı kuralı ve bellek sahipliği yüzeyi de cabası. Saf bir Pascal uniti, kütüphanenin geri kalanının derlendiği her yerde derlenir ve bütün gerekçe budur
ML-DSA bunu alışılmadık derecede ucuzlatır, çünkü tek ilkel bağımlılığı SHAKE'tir. Ne büyük tamsayı katmanı vardır, ne eliptik eğri, ne ayrı bir özet (hash) paketi. PDFlibPas, porttan hemen önceki sürümde akış tabanlı bir XOF kazanmıştı: PDFlibDigest içindeki TPLShakeXOF; PLShakeXOFInit SHAKE128 (rate 168) ya da SHAKE256 (rate 136) seçer, ardından PLShakeXOFAbsorb, PLShakeXOFFinalize ve rastgele çıkış uzunluğu için permütasyona devam eden bir PLShakeXOFSqueeze döngüsü gelir. ML-DSA unitindeki her reddetme-örnekleme yordamı doğrudan bu dört çağrılı API'ye karşı yazılmıştır
Tek motor, üç parametre kümesi: TMLDSAParams
PDFlibPas tüm bir ML-DSA parametre kümesini tek bir kayıtla tanımlar ve küme numarasına göre seçer; böylece ML-DSA-44, 65 ve 87 aynı kod yollarından akar. İlk çalışan uygulama, ML-DSA-44 için sabitlenmiş 4x4 bir derlemeydi; genelleştirmek, k ve l, eta, tau, beta, gamma1 ve gamma2, omega ve zorluk (challenge) uzunluğunu TMLDSAParams'a taşımayı, sonra geri kalan her şeyi türetmeyi anlamına geldi. Genel giriş noktaları üç satırlık sarmalayıcılara dönüştü
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;
// Türetilmiş alanlar hesaplanır, hiçbir zaman tablodan kopyalanmaz
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;
Beş türetilmiş alan, FIPS 204 tablolarından elle kopyalanmak yerine bilerek hesaplanır. Elle yazılmış bit genişlikleri, tam olarak incelemede doğru görünüp üretimde bir fazla ya da eksik olan sabit sınıfıdır ve bu porttaki gerçek kusurlardan ikisi o biçimdeydi. Bildirilen boyutlar doğrulama için adlandırılmış sabitler olarak kalır: ML-DSA-44 için 1312 / 2560 / 2420 baytlık açık anahtar, gizli anahtar ve imza; ML-DSA-65 için 1952 / 4032 / 3309; ML-DSA-87 için 2592 / 4896 / 4627
Sıfırdan bir ML-DSA portu nerede ilk yanlışa gider?
expand_a'da, FIPS 204 Algoritma 32'de; ve hata biçimi alabildiğine yanıltıcıdır. A matrisi, SHAKE128'in rho ile ve ardından iki indeks baytıyla tohumlanmasıyla örneklenir; tohum arabelleği 34 bayttır: rho(32), sonra j, sonra i. 1 tabanlı AnsiString indekslemesiyle Pascal'da yazıldığında o iki bayt Msg[33] ve Msg[34] konumlarına düşer. Bu portun ilk taslağı onları tam bir bayt kaydırmayla Msg[34] ve Msg[35]'e yazdı; sonuç, rhosu test vektörüyle kusursuz eşleşen ama t matrisinin her katsayısı yanlış olan bir anahtar çiftiydi. Kirletilen yalnızca matristi ve matris, açık anahtarın aynen taşımadığı tek şeydir
Aynı yordamda iki kusur daha yaşıyordu. Absorb uzunluğu 35 değil 34 olmalıdır; fazladan tek çöp bayt, sıkıştırılan akışın tamamını değiştirir. Ve iç reddetme döngüsü, bloğun sağlayabildiği her üç baytlık grubu tüketmek zorundadır; buna 168 baytlık bir SHAKE128 bloğunun 165 ofsetinden başlayan grup da dahildir, yani blok başına 56 grup. 162 ofsetinde duran bir çapraz denetim betiği her bloğun kuyruğunu düşürdü ve örneklenen t1 ön ekini yaklaşık on üçüncü bayttan itibaren kaydırdı
SetLength(Msg, 34);
Move(Rho[1], Msg[1], 32);
Msg[33]:= AnsiChar(J); // önce sütun indeksi
Msg[34]:= AnsiChar(I); // sonra satır indeksi
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, 165 ofsetindeki grubu korur: blok başına 56 üçlü
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;
Bu üçü düzeltilince, eksiksiz ML-DSA-44 açık ve gizli anahtarlarının SHA-256 özetleri FIPS 204 known-answer vektörleriyle eşleşti. Bir hata ayıklama dersini de adıyla anmakta yarar var; çünkü bir seansına mal oldu: XOF güdümlü bir reddetme döngüsü için Python çapraz denetimi kurarken hashlib.shake_128().digest(n) akışı sürdürmek yerine her çağrıda aynı ön eki döndürür. Tüm uzunluğu bir kez alın, sonra rate boyutunda bloklara dilimleyin; yoksa referansınız, Pascal'ınızın doğru biçimde reddettiği değerleri memnuniyetle yeniden tüketir
Etayı örnekleme: ML-DSA-65 neden kendi dalına ihtiyaç duyar?
PDFlibPas expand_s içinde iki ayrı yol tutar; çünkü FIPS 204 Algoritma 33 gerçekten ikisini tanımlar. eta = 2 için her nibble 15'e ulaştığında reddedilir, aksi hâlde mod 5 ile küçültülür. eta = 4 için nibble 9 ve üzerinde reddedilir, sonra da hiç modüler küçültme yapılmadan doğrudan kullanılır. ML-DSA-65, eta = 4 ile gelen tek dağıtılan kümedir ve ona mod 5 yolunu yeniden kullanmak s1 ile s2'yi ilk katsayıdan itibaren hizasızlaştırır; sonuç, kendi içinde tutarlı, kendisine karşı doğrulanan ve başkasının ürettiği hiçbir şeyle eşleşmeyen bir anahtar çiftidir
Procedure StoreNibble(Nibble: Byte);
Var
M: Integer;
Centered: Cardinal;
Begin
If Cnt>= N Then
Exit;
If Eta= 4 Then
Begin
If Nibble>= 9 Then // reddet, sonra nibble'ı olduğu gibi al
Exit;
M:= Nibble;
End
Else
Begin
If Nibble>= 15 Then // eta = 2: 15'i reddet, sonra mod 5 ile küçült
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;
Boyutlar testin kendisidir: c-tilde uzunluğu ve gamma1 bit genişliği
İki kodlama parametresi güvenlik düzeyiyle, elinizde çalışan bir ML-DSA-44 derlemesi dururken gözden kaçması kolay biçimlerde değişir. Zorluk özeti c-tilde, 2 x lambda / 8 bayttır; ML-DSA-44 için 32, ML-DSA-65 için 48, ML-DSA-87 için 64. Onu 32'de sabit bırakmak, standart 3309 yerine 3293 baytlık bir ML-DSA-65 imzası üretir ve KAT ön eki hemen sapar. Kayıt alanı CTildaBytes, tam da o sayının unutulamaması için vardır
İkincisi, maske polinomu z'nin paketleme genişliğidir. PDFlibPas bunu üs olarak değil BitWidth(Gamma1) olarak hesaplar: ML-DSA-65 ve 87 için gamma1 = 2^19, katsayı başına 19 değil 20 bit ister ve o tek bit, her z polinomunun 640 bayt mı kapladığını yoksa hiçbir doğrulayıcının ayrıştırmayacağı bir boyut mu tuttuğuna karar verir. Doğrulayıcı, port boyunca eşleşen bir kusur taşıyordu: z serileştirmesinden çıkarma arabelleği 576 yerine 192 boyutluydu. İmza uzunluğu, yazacağınız en ucuz regresyon testidir: Length(Signature)'e karşı 2420, 3309 ve 4627 doğrulayın; parametrelendirme hatalarının çoğu, tek bir kriptografik doğrulamaya ulaşmadan kendini belli eder
Sınırsız döngü olmadan imzalama
ML-DSA imzalama reddetme temellidir; aday bir imza norm ve ipucu (hint) sınamalarını geçene kadar kappa artırılarak yeniden denenir. PDFlibPas bunu 65535 denemelik açık bir dış bütçeyle sınırlar; bütçe tükendiğinde MLDSASignInternal False döndürür ve imzayı boş bırakır, bir belge üretim iş parçacığının içinde dönüp durmak yerine. Pratikte resmi ML-DSA-44 vektörü, 80'lik omega tavanına karşı 55 ipucuyla kappa = 4'te başarıya ulaşır; yani bütçe çalışan bir sınır değil, güvenlik rayıdır
O rayı gerekli hissettiren hata hiç sayısal değildi. İmzalama takılmış gibi görünüyordu, şüphe decompose ve make_hint'e (FIPS 204 Algoritmaları 36 ve 39) düştü ve gerçek neden ters çevrilmiş bir biriktirme hedefiydi: hint hesabına giren vektör c*t0'ı biriktirmek zorundadır, özgün c*t0 ise norm sınaması için el değmemiş kalmalıdır. İkisini de aynı arabelleğe yöneltin ve döngü, aritmetiği kusursuzken sonsuza dek reddeder. Başarı ve bütçe tükenmesi yollarının ikisinde de unit, türetilmiş tohumları, gizli polinomları, maskeleri, zorluğu ve kodlama arabelleklerini sıfırlar; çağıranın verdiği tohum, gizli anahtar ve rnd çağıranın sorumluluğunda kalır — o dizelerin nereden geldiğini bilemeyen bir kütüphane için doğru bölünm budur
ML-DSA bugün PDF imza yığınıyla nerede buluşuyor?
Var olanın ne olduğunu net söyleyin. PDFlibPas ML-DSA'yı doğrulanmış imza ilkel olarak ve bir PKCS #11 mekanizma bağlamayla dağıtır; bugünkü PAdES çıktınızın doğrudan yerine geçen bir çözüm olarak değil. Token yolu TPDFlibPKCS11Client.SignMLDSA'dır ve bilinçli olarak ayrı bir giriş noktasıdır; çünkü CKM_ML_DSA önceden hesaplanmış özet yerine ham mesajı tüketir, dolayısıyla mevcut SignHash ve dış özet geri çağırmaları yeniden kullanılamaz. Sertifikasız keşif, CertificateOptional'ın özel anahtar etiketi ya da kimliğiyle birlikte açıkça etkinleştirilmesini ister ve istemci, CKA_PARAMETER_SET'i bağlanma anında CKP_ML_DSA_44 / 65 / 87 beyaz listesine karşı doğrular; böylece varsayılan RSA ve ECDSA sertifika eşleştirmesi kazara gevşetilmez
Belge düzeyi bütünleştirme, kütüphane kodundan çok standartlar çalışmasıyla yönetilen kısımdır. ISO 32000-2 §12.8 imza sözlüğünü ve CMS yükünü tanımlar; ISO/TS 32002, o desteği yeni özet ve imza algoritmalarına taşıyan araçtır. Doğrulayıcılarınız ve karşı taraflarınız uymadıkça klasik imzalama üretim yolu olarak kalır. Pratik duruş paralel hatlardır: bugün bir üçüncü tarafın doğrulaması gereken her şey için zaman damgalı ve uzun vadeli doğrulama verili PAdES B-B'den B-LTA imzalarına devam ederken, yanında ML-DSA anahtar yönetimi ve token bütünleştirmesini kanıtlarsınız. Yerel denemeler için aynı CryptoAPI üzerine kurulu kendinden imzalı sertifika akışı size kamusal bir CA'ya başvurmadan imzalama kimliği verir
Bir parametre kümesi değişikliğini, başka herhangi bir imzalama değişikliğini sınadığınız gibi sınayın. Önce boyutlar, sonra resmi vektörler, sonra negatif durumlar: elle oynanmış bir imza baytı, eşleşmeyen bir context dizisi, kesilmiş bir anahtar. PDFlibPas bunların hepsini DUnitX takımında kapsar ve aynı disiplin kendi hattınıza da aittir; ideal olarak belge külliyatında doğrulamayı toplu yürüten uyum ve imzalama çalışma tezgâhının yanında, böylece bir regresyon fark edilmeden müşteriye asla ulaşmaz
Belge yazılımı için kuantum sonrası hazırlık tek bir anahtar olarak gelmeyecek. Sınayabileceğiniz ilkiller, bağlayabileceğiniz bir token yolu ve güncel sürümü riske atmaksızın izleyeceğiniz bir standartlar hattı olarak gelir. ML-DSA unitinin, yerel bir Object Pascal kod tabanındaki imzalama, şifreleme ve PDF/A araçlarının gerisiyle nasıl yan yana durduğunu görmek için PDFlibPas Delphi PDF library ürün sayfası, bileşen setinin tamamını ve desteklenen derleyici matrisini listeler