Μια υπογραφή PDF είναι κυρίως λογιστική byte, και εκεί είναι που πάει στραβά. Η κρυπτογραφία τρέχει σε κώδικα που έχει ελεγχθεί για δύο δεκαετίες, και αυτό το κομμάτι σχεδόν ποτέ δεν αποτυγχάνει. Αυτό που αποτυγχάνει στην παραγωγή είναι πιο ταπεινό: ένα placeholder δεσμευμένο πολύ μικρό για την πραγματική υπογραφή, ένα hash που λήφθηκε πάνω από λάθος τμήμα του αρχείου, ή ένα "save" μετά την υπογραφή που σιωπηλά ξανάγραψε bytes που η υπογραφή είχε ήδη παγώσει. Τακτοποιήστε σωστά τα bytes και το πράσινο σημάδι επιβεβαίωσης φροντίζει μόνο του
Το HotPDF καλύπτει την υπογραφή για Delphi και C++Builder σε τρία επίπεδα, και επιλέγετε ανάμεσά τους απαντώντας σε μία ερώτηση: πού κατοικεί το ιδιωτικό κλειδί; Ένα αρχείο PFX στον δίσκο χρειάζεται μία μόνο κλήση συνάρτησης. Ένα κλειδί κλειδωμένο σε ένα HSM ή μια απομακρυσμένη υπηρεσία υπογραφής χρειάζεται την ακολουθία reserve-hash-insert, επειδή καμία βιβλιοθήκη δεν μπορεί να φτάσει μέσα σε ένα token και να τραβήξει έξω το κλειδί. Μια υπογραφή που πρέπει να ικανοποιεί ευρωπαϊκό κανονισμό χρειάζεται επιπλέον τις δομές γραμμής βάσης PAdES. Οι παρακάτω ενότητες ακολουθούν αυτή την πρόοδο
Πώς το /ByteRange καθηλώνει τα υπογεγραμμένα bytes
Μια υπογραφή πρέπει να ζει μέσα στο ίδιο το αρχείο που υπογράφει, και δεν μπορεί να υπογράψει τον εαυτό της. Το PDF παρακάμπτει το παράδοξο αφήνοντας μια τρύπα. Πριν την υπογραφή, ο εγγραφέας δεσμεύει μια καταχώριση /Contents σταθερού μεγέθους γεμάτη με μηδενικά και καταγράφει έναν πίνακα /ByteRange για τα δύο τμήματα εκατέρωθεν της: όλα πριν την τρύπα, όλα μετά. Ο υπογράφων κάνει hash σε αυτά τα δύο τμήματα και γράφει το προκύπτον blob CMS μέσα στην τρύπα ως δεκαεξαδικό. Η παγίδα βρίσκεται στη λέξη fixed (σταθερό). Δεσμεύεστε στο μέγεθος αυτής της τρύπας προτού γνωρίζετε πόσο μεγάλη θα είναι η ολοκληρωμένη υπογραφή, οπότε η δέσμευση πρέπει να είναι μια σίγουρη υπερεκτίμηση. Οκτώ kilobyte χωρούν άνετα μια αποσπασμένη υπογραφή CMS με μια σύντομη αλυσίδα πιστοποιητικών
Το HotPDF χωρίζει τις δύο περιπτώσεις σε δύο κλήσεις, και η σύγχυσή τους είναι ένα συνηθισμένο πρώιμο λάθος. Το AddSignatureField τοποθετεί ένα κενό, ορατό πεδίο για να το υπογράψει αργότερα ένα άτομο σε ένα πρόγραμμα προβολής. Το AddSignedSignatureField δημιουργεί το πεδίο και δεσμεύει την τρύπα /Contents, που είναι αυτό που θέλετε όποτε ο κώδικας, και όχι ένας άνθρωπος, θα ολοκληρώσει την υπογραφή. Δώστε σε έναν εξωτερικό υπογράφοντα ένα κενό πεδίο και δεν θα έχει τίποτα να συμπληρώσει
Η διαδρομή μίας κλήσης: υπογραφή από PFX
Όταν το πιστοποιητικό και το ιδιωτικό του κλειδί βρίσκονται σε ένα αρχείο PFX/PKCS#12 που η διεργασία σας μπορεί να διαβάσει, όλη η ροή επεξεργασίας συρρικνώνεται σε μία συνάρτηση κλάσης:
if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
'company-cert.pfx', 'pfx-password') then
Writeln('Signed: invoice-signed.pdf')
else
raise Exception.Create('PFX signing failed');
Όταν αυτό αποτυγχάνει, σπάνια φταίει το PDF. Φταίει το PFX. Το HotPDF διαβάζει containers προστατευμένα με PBES2, δηλαδή παραγωγή κλειδιού PBKDF2 πάνω σε AES-256-CBC. Ένα PFX που εξήχθη από έναν παλαιότερο οδηγό πιστοποιητικών των Windows, ή από OpenSSL πριν την έκδοση 3.0, είναι συνήθως τυλιγμένο αντ' αυτού σε παλαιό RC2 ή 3DES, και απλά δεν θα αναλυθεί. Η λύση είναι να επανεξάγετε το container μία φορά με σύγχρονη προστασία· το σημερινό OpenSSL το κάνει αυτό εξ ορισμού, και δεν είναι αλλαγή κώδικα. Οπότε όταν η υπογραφή αποτυγχάνει αμέσως σε ένα πιστοποιητικό που "δουλεύει παντού αλλού", κοιτάξτε πώς δημιουργήθηκε το PFX προτού υποψιαστείτε τον δικό σας κώδικα
Η διαδρομή reserve-hash-insert για HSM και tokens
Η διαδρομή μίας κλήσης υποθέτει ότι η διεργασία σας μπορεί να διαβάσει το κλειδί ως αρχείο. Ολοένα και περισσότερο δεν μπορεί. Το κλειδί βρίσκεται σε ένα HSM, σε ένα USB token, ή πίσω από το API μιας υπηρεσίας υπογραφής, και δεν υπάρχει τρόπος για μια βιβλιοθήκη να το φτάσει απευθείας. Το HotPDF το χειρίζεται αυτό σπάζοντας την υπογραφή σε βήματα επιπέδου byte: γράψτε ένα έγγραφο-placeholder, ζητήστε από τη βιβλιοθήκη τα εύρη hash, περάστε την είσοδο hash σε ό,τι κρατά το κλειδί, και έπειτα εμβολιάστε το επιστρεφόμενο CMS πίσω στην τρύπα
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Εγγραφή του εγγράφου με δεσμευμένη τρύπα /Contents
Doc := THotPDF.Create(nil);
try
Doc.FileName := 'placeholder.pdf';
Doc.BeginDoc;
Doc.CurrentPage.AddSignedSignatureField('Sig1',
Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
'Contract approval', 'Boston, MA', 'legal@example.com');
Doc.EndDoc;
finally
Doc.Free;
end;
// 2. Φόρτωση των αποθηκευμένων bytes· τα επιστρεφόμενα offsets ξεκινούν από το μηδέν
Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
try
SetLength(PdfBytes, Fs.Size);
Fs.ReadBuffer(PdfBytes[1], Fs.Size);
finally
Fs.Free;
end;
THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
CStart, CLen);
// 3. Hash και στα δύο τμήματα και εξωτερική υπογραφή (HSM, token, υπηρεσία)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // η δική σας ενσωμάτωση: επιστρέφει το CMS ως hex
// 4. Εμβολιασμός της υπογραφής μέσα στη δεσμευμένη τρύπα
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Δύο λεπτομέρειες σε αυτή την ακολουθία προκαλούν τις περισσότερες διαλείπουσες αποτυχίες. Η πρώτη είναι ότι το PreparePDFForSigning λειτουργεί πάνω στα bytes ενός ολοκληρωμένου αρχείου. Το placeholder πρέπει να έχει γραφτεί και αποθηκευτεί πλήρως προτού τα offsets σημαίνουν κάτι· υπολογίστε τα πάνω σε ένα stream που ακόμα συναρμολογείται και δεν θα ευθυγραμμιστούν με τα bytes που τελικά κάνετε hash. Η δεύτερη είναι το μέγεθος της δέσμευσης, ξανά. Τα 8192 bytes που ζητήσατε πρέπει να χωράνε το τελικό CMS, και μια υπογραφή που μεταφέρει ενδιάμεσα πιστοποιητικά, ή μία που μια υπηρεσία διακοσμεί με υπογεγραμμένα attributes, μπορεί να το ξεπεράσει. Το InsertSignatureHex δεν θα μεγαλώσει την τρύπα για να κάνει χώρο. Το σύμπτωμα είναι μια ροή επεξεργασίας που υπογράφει κανονικά με ένα πιστοποιητικό και αποτυγχάνει με το επόμενο· η θεραπεία είναι να αναδημιουργήσετε το placeholder με μια δέσμευση μετρημένη από μια πραγματική υπογραφή που παρήγαγε ο πραγματικός υπογράφων, όχι μαντεμένη
Γραμμές βάσης PAdES, και οι χρονοσφραγίδες που κρατούν μια υπογραφή ζωντανή
Αν υπογράφετε σύμφωνα με ευρωπαϊκούς κανόνες, το πρότυπο σε ισχύ είναι το ETSI EN 319 142-1, το οποίο στοιβάζει τέσσερα επίπεδα γραμμής βάσης PAdES. Το B-B είναι η απλή υπογραφή. Το B-T προσθέτει μια έμπιστη χρονοσφραγίδα που αποδεικνύει πότε δημιουργήθηκε. Το B-LT ενσωματώνει το υλικό επικύρωσης, τα πιστοποιητικά και τα δεδομένα ανάκλησης, μέσα στο έγγραφο ώστε να μπορεί να ελεγχθεί ακόμη και χρόνια αργότερα. Το B-LTA στοιβάζει περιοδικές χρονοσφραγίδες εγγράφου από πάνω, ώστε το αποδεικτικό υλικό να επιβιώνει των αλγορίθμων πάνω στους οποίους χτίστηκε. Το HotPDF εκπέμπει τις δομές από την πλευρά του εγγράφου για κάθε επίπεδο:
// Πεδίο υπογραφής γραμμής βάσης PAdES (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Χρονοσφραγίδα εγγράφου: μεγαλύτερη δέσμευση για το token και την αλυσίδα του TSA
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
Η δέσμευση 16384 byte στη χρονοσφραγίδα είναι σκόπιμη. Μια αρχή χρονοσφράγισης επιστρέφει ένα token που σέρνει μαζί του τη δική του αλυσίδα πιστοποιητικών, οπότε συνήθως χρειάζεται περισσότερο χώρο από τα 8 KB με τα οποία αρκείται μια απλή υπογραφή. Αυτές οι χρονοσφραγίδες εγγράφου είναι επίσης ο μηχανισμός πίσω από το B-LTA: η επανα-χρονοσφράγιση μιας αρχειοθετημένης υπογραφής κάθε λίγα χρόνια, με αλγορίθμους που παραμένουν σύγχρονοι, είναι αυτό που κρατά ένα έγγραφο που υπογράψατε το 2026 επαληθεύσιμο το 2040
Μια λέξη για τις συμβολοσειρές reason, location και contact που δέχονται και οι δύο κλήσεις πεδίου: είναι μεταδεδομένα ευκολίας και τίποτα παραπάνω. Το HotPDF τις αποθηκεύει ως απλές καταχωρίσεις λεξικού και τις ζωγραφίζει μέσα στην ορατή εμφάνιση της υπογραφής, αλλά κανένας επαληθευτής δεν τις ελέγχει έναντι τίποτα. Συμπληρώστε τις με συνέπεια από τα δεδομένα της ροής εργασίας σας, καθώς οι ελεγκτές πράγματι τις διαβάζουν, αλλά ποτέ μην τις μπερδέψετε με αποδεικτικό υλικό. Η πραγματική κρυπτογραφική διεκδίκηση κατοικεί εξολοκλήρου στο CMS και στην αλυσίδα πιστοποιητικών του, και ένας επαληθευτής αγνοεί εντελώς το ορατό κείμενο
Μετά την υπογραφή, το αρχείο μπορεί μόνο να μεγαλώσει
Τη στιγμή που υπάρχει μια υπογραφή, τα bytes μέσα στα εύρη της παγώνουν. Ο μόνος νόμιμος τρόπος να αλλάξετε το αρχείο αργότερα είναι μια αυξητική ενημέρωση (incremental update) κατά ISO 32000-1 §7.5.6, η οποία προσθέτει νέα και τροποποιημένα αντικείμενα μετά τα αρχικά bytes και αλυσιδώνει μια νέα ενότητα cross-reference πίσω σε αυτά. Έτσι όπως γίνεται, η υπογραφή παραμένει έγκυρη για τη δική της αναθεώρηση και ένα πρόγραμμα προβολής αναφέρει την ειλικρινή κατάσταση: η υπογεγραμμένη αναθεώρηση παραμένει ανέπαφη, το έγγραφο επεκτάθηκε αργότερα. Επαναδιαδοχοποιήστε αντίθετα ολόκληρο το αρχείο και θα ξαναγράψετε τα υπογεγραμμένα τμήματα, κάτι που καταστρέφει την υπογραφή ακόμη κι όταν τίποτα ορατό δεν άλλαξε. Ο ίδιος μηχανισμός αναθεωρήσεων είναι επίσης ο τρόπος με τον οποίο ένα έγγραφο φέρει πολλαπλές υπογραφές: κάθε νέα υπογραφή προσγειώνεται στη δική της αυξητική ενημέρωση, και τα εύρη της καλύπτουν όλα όσα προηγούνται, συμπεριλαμβανομένων των προηγούμενων υπογραφών. Οι μηχανισμοί μόνο-προσάρτησης, και το πότε είναι ασφαλές να συμπτυχθούν, καλύπτονται στο άρθρο για τα object streams και τις αυξητικές ενημερώσεις
Αξίζει να έχετε κατά νου δύο όρια όσο σχεδιάζετε. Η λειτουργία εξόδου PDF/A του HotPDF απορρίπτει εντελώς τα πεδία υπογραφής, οπότε η συμμόρφωση αρχειοθέτησης και μια ενσωματωμένη υπογραφή πρέπει να κυκλοφορούν ως ξεχωριστά αρχεία. Και η υπογραφή δεν λέει τίποτα για τη μυστικότητα: αποδεικνύει ποιος παρήγαγε ένα έγγραφο και ότι δεν έχει αλλάξει έκτοτε, αλλά οποιοσδήποτε μπορεί ακόμη να το διαβάσει. Η απόκρυψη του περιεχομένου είναι ξεχωριστή δουλειά, την οποία χειρίζεται η κρυπτογράφηση AES-256 και η πολιτική δικαιωμάτων
Ό,τι κι αν χτίζετε, δοκιμάστε το με κάτι διαφορετικό από τον κώδικα που έγραψε το αρχείο. Ανοίξτε την έξοδο στον πίνακα υπογραφών του Acrobat και επιβεβαιώστε τρία πράγματα: η υπογραφή είναι έγκυρη, η ταυτότητα αλυσιδώνεται στη ρίζα που περιμένατε, και ο πίνακας αναφέρει καμία αλλαγή από την υπογραφή. Έπειτα αλλάξτε ένα μόνο byte μέσα στο υπογεγραμμένο εύρος ενός αναλώσιμου αντιγράφου και επιβεβαιώστε ότι ο πίνακας τώρα δηλώνει το έγγραφο αλλοιωμένο. Μια ροή υπογραφής που δεν έχετε δει ποτέ να απορρίπτει ένα παραποιημένο αρχείο είναι μια ροή της οποίας η επαλήθευση δεν έχει πράγματι δοκιμαστεί
Και τα τρία επίπεδα υπογραφής κυκλοφορούν με το HotPDF Delphi Component για Delphi και C++Builder· η σελίδα προϊόντος συνδέει την πλήρη αναφορά API υπογραφών