HotPDF מאמתת חתימות CMS של ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 ו-Ed448 במסמכי PDF טעונים, וחותמת דרך ספקים נשלפים כך שהמפתח הפרטי אף פעם לא חייב לחיות בתוך תהליך הדלפי שלך. המחצית השנייה היא החלק שרוב הצוותים זקוקים לו תחילה. טוקן חומרה, שירות חתימה מרוחק וכרטיס eID לאומי כולם מסרבים למסור מפתח, ועד שצינור החתימה מופרד ממאגר המפתחות, אף אחד מהם אינו שמיש כלל
ההפרדה היא העניין ב-THPDFSignatureProvider. HotPDF שומרת על החלקים שעליה להחזיק בהם — ניתוח CMS, בניית SignedData, פריסת /ByteRange — ומאצילה את הפעולה היחידה שאינה יכולה להחזיק בה, שהיא הפיכת תמצית לחתימה עם מפתח שאסור לה לראות. כל מהלהלן נובע מחלוקה זו
למה חתימת ML-DSA תקפה נכשלת באימות?
מפני ש-HotPDF מסרבת ל-ML-DSA על מסמך טעון שאינו מצהיר על ההרחבה עבורו. ML-DSA — תכנית החתימה הסריגית שתוקננה כ-FIPS 204, והסיבה שאנשים אומרים "PDF פוסט-קוונטי" — אין לה עדיין רישום ISO 32000-2. PDF שנושא אחת משתמש באלגוריתם שתקן הבסיס אינו מזכיר, וקובץ שמשתמש בשקט באלגוריתם ללא-שם הוא קובץ שפסק הדין שלו אינו יכול להיות מופק על ידי אף אחד אחר
לכן HotPDF הופכת את הטענה למפורשת. EnsureMLDSAExtensions מעלה את המסמך ל-PDF 2.0 כאשר מותר וכותבת /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> לתוך הקטלוג. בצד הקריאה, LoadedDocumentDeclaresMLDSAExtension מדווחת אם ההצהרה הזו שרדה, ו-VerifyLoadedSignatureWithOptions מחילה את אותה בדיקה לפני שתכבד את Options.AllowMLDSA. הגדר את הדגל על מסמך לא-מוצהר והוא נשאר כבוי — האפשרות יכולה לרפות מדיניות, לעולם לא את הדרישה המבנית
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'contract-pq.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
Pdf.EnsureMLDSAExtensions; // declare before the signature is written
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
יש לקרוא לזה לפני השמירה, לא אחריה. ההצהרה היא חלק מטווח הבייטים החתום, וקטלוג שתוקן לאחר מכן הוא או שינוי לא-חתום בקובץ חתום או רוויזיה שנייה שמאמת ידווח עליה כשינוי
שלוש משפחות אלגוריתמים, נקודת כניסה אחת לאימות
כל שלוש המשפחות מגיעות דרך VerifyLoadedSignatureWithOptions, שמקבלת אינדקס חתימה, את זרם המקור, רשומת THPDFCMSVerifyOptions ופרמטר out לפרטי החתימה. לרשומה יש בדיוק שלושה שדות, וכל אחד עונה על שאלה שנהגה לדרוש בנייה מחדש
SignatureProvider מחליף ספק משלך במקום זה המובנה של הפלטפורמה. OpenSSLLibraryPath בוחר ספריית OpenSSL 3, שהיא מה שמספק את אימות Ed25519 ו-Ed448 במצב pure ש-Windows CNG אינו מציע בכל מקום. AllowMLDSA מצטרף לאלגוריתמים הסריגיים, בכפוף לבדיקת ההרחבה לעיל. ה-OID המדויק של האלגוריתם שזוהה חוזר ב-THPDFSignatureInfo.SignatureAlgorithmOID, כך שיומן ביקורת יכול לרשום מה אומת במקום מה התבקש
var
Opts: THPDFCMSVerifyOptions;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
Src: TFileStream;
begin
Opts := THPDFCMSVerifyOptions.Default;
Opts.OpenSSLLibraryPath := 'C:\openssl3\libcrypto-3-x64.dll';
Opts.AllowMLDSA := Pdf.LoadedDocumentDeclaresMLDSAExtension;
Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
if Status = svValid then
Memo1.Lines.Add('signed with OID ' + string(Info.SignatureAlgorithmOID));
finally
Src.Free;
end;
end;
Ed25519 ו-Ed448 אינם זקוקים להצהרת הרחבה, מפני ש-ISO 32000-2 כבר מקבל אותם. הם כן זקוקים לספק שמיישם אותם, מה שברוב פריסות Windows פירושו הפניית OpenSSLLibraryPath לספרייה שאתה מספק ושולט בה במקום למה שקורה להיות על המכונה
למה באמת מתחייב ספק חתימה?
ספק מתחייב על דבר אחד: בהינתן בקשה, להחזיר מצב ובעת חתימה גם בייטים. THPDFSignatureProviderRequest נושא את האלגוריתם וה-OID שלו, את OID של התמצית, את אורך המלח של PSS, האם הקלט הוא הודעה או תמצית שכבר חושבה, את הקלט עצמו, את המפתח הציבורי או התעודה, מזהה מפתח ומזהה פעולה. שום דבר ברשומה זו אינו ספציפי ל-HotPDF — זה האוצר שבו מנהל התקן של טוקן או שירות חתימה כבר מדבר
שלושה מימושים מגיעים עם הספרייה. THPDFCallbackSignatureProvider עוטף שיטות אנונימיות, שזה הנתיב הקצר ביותר משגרת חתימה פנימית-ארגונית קיימת לחתימת PDF עובדת. THPDFRemoteSignatureProvider עוטף callback תעבורה עם מגבלת ניסיון חוזר, רישום ביטול וגבולות על גודל הקלט והחתימה, כך ש-HSM תקוע אינו יכול להפוך ליישום תקוע. THPDFPKCS11SignatureProvider מבצע סריאליזציה של פעולות RSA כנגד סשן PKCS#11 בבעלות הקורא שכבר אומת ומצביע מפתח-פרטי — HotPDF לעולם לא מתחברת, לעולם לא רואה PIN, ולעולם לא סוגרת סשן שלא פתחה
var
Provider: THPDFRemoteSignatureProvider;
begin
Provider := THPDFRemoteSignatureProvider.Create(
function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
out Signature: TBytes): THPDFSignatureProviderStatus
begin
// POST Req.Input to the signing service; Req.KeyIdentifier selects the key
if PostToSigningService(Req.KeyIdentifier, Req.Input, Signature) then
Result := spsValid
else
Result := spsProviderError;
end,
3, // RetryLimit
1048576, // MaxInputBytes
65536); // MaxSignatureBytes
try
// hand Provider to the signing call
finally
Provider.Free;
end;
end;
למה ל-enum של המצב יש שישה ערכים במקום בוליאני
THPDFSignatureProviderStatus מבחין בין spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError ו-spsCancelled, וקריסתם עולה לך ביכולת לפעול נכון. חתימה שגויה קריפטוגרפית (spsInvalid) היא אירוע אבטחה. אלגוריתם שהספק אינו מיישם (spsUnsupported) הוא פער פריסה. כשל תעבורה (spsProviderError) כדאי לנסות שוב, ובקשת טוקן שבוטלה על ידי המשתמש (spsCancelled) אינה כדאית לניסיון חוזר כלל
הכלל לחתימה צר: ספק חתימה מחזיר spsValid רק עם חתימה לא-ריקה. ספקי אימות מחזירים spsValid או spsInvalid, והארבעה האחרים נשארים נפרדים בשני הנתיבים. אם אתה כותב ספק, התנגד לפיתוי למפות כל דבר שאינך מזהה אל spsInvalid — זה הופך DLL חסר לדוח שהחתימה של הלקוח מזויפת
היכן החתימה באמת נוחתת בקובץ
שתי פונקציות מחברות ספקים לבייטים אמיתיים של PDF. HPDFCMSBuildSignedDataWithProvider בונה CMS detached מתמצית SHA-256 של מסמך, שזו נקודת הכניסה הנכונה כשזרימת העבודה שלך מחשבת את התמצית במקום אחר. HPDFCMSSignPDFStreamWithProvider חותמת placeholder חתימה קיים בזרם PDF ושומרת על צינור /ByteRange הסטנדרטי, שזו נקודת הכניסה הנכונה כש-HotPDF פרסה את ה-placeholder עצמה
שמירת אותו צינור חשובה יותר ממה שזה נשמע. מוסכמת /ByteRange — שני טווחים שמדלגים על חלון החתימה ההקסדצימלית — היא מה שכל מאמת בודק ראשון, ונתיב מונע-ספק ששכתב אותה היה שובר התאמת PAdES ללא קשר לכמה שהקריפטוגרפיה תקינה. HotPDF שומרת על הפריסה זהה לנתיב החתימה המובנה, כך שמסמך שנחתם דרך טוקן PKCS#11 מאומת עם אותו קוד אימות חתימות כמו זה שנחתם מקובץ PFX. לכללי הפרופיל שיושבים מעל בחירת האלגוריתם, ראה הסקירה של חתימות בסיס PAdES בדלפי, ולמלכודות הקידוד הספציפיות ל-ECDSA שקדמו למודל ספק זה, ההערות על אימות ECDSA CMS ופורמטי חתימה P1363
סדר הגירה שאינו משאיר את המסמכים שלך תקועים
מוכנות פוסט-קוונטית היא בעיית לוח זמנים, לא מתג. כמעט אף קורא PDF שמבוסס אינו מאמת ML-DSA כיום, כך שמסמך שנחתם איתו לבד הוא, מנקודת מבטו של הקורא, מסמך עם חתימה שאינה ניתנת לאימות. הסדר ששורד מגע עם ארכיונים אמיתיים הוא: השאר את RSA או ECDSA כחתימה שמאמת ישפוט, הוסף את הצהרת ההרחבה וחתימת ML-DSA שנייה כאשר מדיניות דורשת ראיה עמידה לקוונטים, והעבר את החתימה הראשית רק כשמערכות הצריכה השיגו
מה ש-HotPDF נותנת לך היום הוא היכולת לכתוב ולאמת את שתיהן, מאותו קוד, עם האלגוריתם רשום בכנות בקובץ ובתוצאת האימות. HotPDF היא רכיב PDF VCL מקורי לדלפי ו-C++Builder ללא סביבת ריצה חיצונית של PDF, כך שנתיבי החתימה והאימות מובלעים בתוך קובץ ההפעלה שלך במקום לצידו — ראה את דף רכיב ה-HotPDF לדלפי לרשימת התכונות המלאה והורדת הניסיון