Πάρτε ένα τιμολόγιο PDF που ήδη φέρει κρυπτογράφηση AES-256 και ζητήστε από το PDFium Component για Delphi και C++Builder (PDFiumPas) να το σφραγίσει PDF/A για αρχειοθέτηση, ή να το υπογράψει με PAdES, μέσω μιας επαυξητικής ενημέρωσης αντί για μια πλήρη επανεγγραφή. Η βιβλιοθήκη δεν θα φτάσει εκεί μπαλώνοντας απευθείας τα κρυπτογραφημένα byte: οι έξι εγχυτές σημαδιών συμμόρφωσής της εντοπίζουν μια υπάρχουσα καταχώριση /Encrypt και περνούν την πηγή προς τα εμπρός αμετάβλητη byte προς byte, και ο υπογράφων PAdES της εγείρει εξαίρεση αντί να εκπέμψει μια υπογραφή που κανένας επικυρωτής δεν θα δεχτεί
Αυτό είναι διαφορετικό ερώτημα από τον έλεγχο ενός PDF που δεν δημιουργήσατε εσείς για κρυμμένο κίνδυνο, κάτι που είναι η δική του άσκηση μόνο ανάγνωσης. Αυτό το άρθρο αφορά την πλευρά εγγραφής του ίδιου ορίου εμπιστοσύνης: τι επιτρέπεται να κάνει ο δικός σας κώδικας σε ένα αρχείο του οποίου τα byte είναι ήδη κλειδωμένα πίσω από τον κωδικό πρόσβασης κάποιου άλλου, τη στιγμή που εκείνος ο κώδικας προσπαθεί να προσθέσει οτιδήποτε σε αυτό εκ των υστέρων
Τι απαιτεί το ISO 32000-1 όταν ενημερώνετε ένα κρυπτογραφημένο PDF;
Το ISO 32000-1 §7.5.6 απαιτεί το trailer μιας επαυξητικής ενημέρωσης να επαναλαμβάνει κάθε καταχώριση από το προηγούμενο trailer εκτός από το /Prev, και ο Πίνακας 15 απαριθμεί το /Encrypt μεταξύ των καταχωρίσεων που μπορεί να φέρει ένα trailer. Αφαιρέστε το από το νέο trailer και ένας συμμορφούμενος αναγνώστης δεν έχει λόγο να αμφισβητήσει την παράλειψη: το νεότερο trailer είναι αυθεντικό, οπότε ένας αναγνώστης που δεν βρίσκει εκεί κανένα /Encrypt αποφασίζει ότι ολόκληρο το αρχείο είναι μη κρυπτογραφημένο και προσπαθεί να αναλύσει το παλαιότερο, ακόμη κρυπτογραφημένο σώμα ως απλά byte. Κρατήστε το /Encrypt στο νέο trailer αλλά γράψτε τα δικά της αντικείμενα της ενημέρωσης ως απλό κείμενο, και η αποτυχία απλώς μετακινείται ένα βήμα αργότερα: ο αναγνώστης εντοπίζει σωστά την κρυπτογράφηση, περνά κάθε αντικείμενο που αγγίζει μέσα από τον κρυπτογράφο του αρχείου, συμπεριλαμβανομένων των νέων που ποτέ δεν κρυπτογραφήθηκαν εξαρχής, και παίρνει πίσω θόρυβο για περιεχόμενο που ήταν τέλεια αναγνώσιμο πριν το αγγίξει η αποκρυπτογράφηση. Οποιοδήποτε από τα δύο λάθη παράγει ένα αρχείο που μοιάζει με μια κανονική, καλά σχηματισμένη επαυξητική ενημέρωση σε επίπεδο byte, μέχρι τη στιγμή που το ανοίγει ένας συμμορφούμενος αναγνώστης
Έξι εγχυτές σημαδιών, μία πύλη κρυπτογράφησης v2.14.2
Το PDFiumPas διανέμει έξι εγχυτές σημαδιών σε επίπεδο byte, έναν για κάθε υποσύνολο PDF κατά ISO που μπορεί να επισημάνει: PDF/A (ISO 19005), PDF/X (ISO 15930), PDF/UA (ISO 14289-1), PDF/E-1 (ISO 24517-1), PDF/R-1 (ISO 23504-1), και PDF/VT-1 (ISO 16612-2). Καθένας παίρνει τα byte που ήδη έγραψε το δικό του FPDF_SaveAsCopy του PDFium και στρώνει μια δεύτερη, μικρότερη επαυξητική ενημέρωση πάνω τους: μια νέα ροή μεταδεδομένων XMP, μια επεξεργασία λεξικού καταλόγου που δείχνει σε αυτήν, και για τα υποσύνολα προσανατολισμένα σε εκτύπωση ένα OutputIntent και προφίλ ICC. Από την v2.14.2, καθένα από τα InjectPdfAMarkers, InjectPdfXMarkers, InjectPdfUaMarkers, InjectPdfEMarkers, InjectPdfRMarkers, και InjectPdfVTMarkers διαβάζει πρώτα το trailer πηγή, και αν αναφέρει μια υπάρχουσα καταχώριση /Encrypt, αντιγράφει την πηγή προς τη ροή προορισμού αμετάβλητη και επιστρέφει αμέσως. Κανένα XMP, κανένα OutputIntent, καμία επεξεργασία καταλόγου — ο καλών παίρνει πίσω το αρχικό αρχείο, byte προς byte
var
Src, Dst: TFileStream;
Opts: TPdfXSaveOptions;
begin
Src := TFileStream.Create('signed-encrypted-proof.pdf', fmOpenRead);
Dst := TFileStream.Create('pdfx-attempt.pdf', fmCreate);
try
Opts.Conformance := pxc4;
InjectPdfXMarkers(Src, Dst, Opts);
// pdfx-attempt.pdf is byte-identical to the source: still encrypted,
// no /GTS_PDFXVersion, no OutputIntent. Nothing was written, and
// nothing was corrupted either
finally
Dst.Free;
Src.Free;
end;
end;
Το επιτρεπτό να είναι κρυπτογραφημένο δεν είναι το ίδιο με το ασφαλές προς έγχυση
Τόσο το PDF/E-1 όσο και το PDF/R-1 επιτρέπουν ρητά το έγγραφο host τους να είναι κρυπτογραφημένο σε επίπεδο προδιαγραφής, κάτι που διαβάζεται σαν εξαίρεση μέχρι να κοιτάξετε τι πράγματι πρέπει να συμβεί στον δίσκο. Το ISO 24517-1 §6.3 επιτρέπει κρυπτογράφηση για PDF/E-1, και το ISO 23504-1 §6.2.3 την επιτρέπει για PDF/R-1 υπό την προϋπόθεση ότι η κεφαλίδα δηλώνει %PDF-2.0. Καμία ρήτρα δεν λέει τίποτα για το αν ένας επεξεργαστής μετά-παραγωγής σε επίπεδο byte μπορεί να προσθέσει με ασφάλεια ένα αντικείμενο απλού κειμένου σε εκείνο το κρυπτογραφημένο δοχείο, και δεν μπορεί, για τους ίδιους λόγους §7.5.6 που ισχύουν για κάθε άλλο υποσύνολο. Οι δικοί τους επικυρωτές συμμόρφωσης του PDFiumPas για αυτά τα δύο προφίλ, ValidatePdfECompliance και ValidatePdfRCompliance, καταγράφουν την παρουσία του /Encrypt σκόπιμα χωρίς να το σημαίνουν ως ελάττωμα, κάτι που είναι σωστό για έναν επικυρωτή μόνο-ανάγνωσης που ποτέ δεν γράφει ούτε ένα byte. Είναι επίσης ένα μοτίβο εύκολο να το προσπεράσετε στη γρήγορη ανάγνωση και να υποθέσετε ότι ο αδελφικός εγχυτής δεν χρειάζεται ξεχωριστή φρουρά, ενώ ο εγχυτής είναι η μία συνάρτηση στο ζεύγος που πράγματι πρέπει να αρνηθεί
Το SaveAsPdfX αποκρυπτογραφεί σιωπηλά το έγγραφό σας;
Ναι, όποτε περνάτε μέσα από τις δημόσιες μεθόδους ευκολίας αντί να καλείτε απευθείας έναν εγχυτή. Καθένα από τα TPdf.SaveAsPdfA, SaveAsPdfX, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfR, και SaveAsPdfVT αποδίδει το τρέχον έγγραφο σε μια προσωρινή ροή με SaveAs(Tmp, saRemoveSecurity) πριν παραδώσει εκείνα τα byte στον αντίστοιχο εγχυτή του. Το saRemoveSecurity αντιστοιχίζεται στη δική της σημαία FPDF_REMOVE_SECURITY του PDFium, οπότε το προσωρινό αντίγραφο που λαμβάνει ο εγχυτής ποτέ δεν ήταν κρυπτογραφημένο εξαρχής, και η φρουρά /Encrypt του εγχυτή ποτέ δεν έχει λόγο να πυροδοτηθεί. Η έξοδος φέρει τα σημάδια σας PDF/A, PDF/X, PDF/UA, PDF/E-1, PDF/R-1, ή PDF/VT-1, αλλά δεν προστατεύεται πλέον από όποιον κωδικό πρόσβασης άνοιξε την πηγή
Εκείνος ο συμβιβασμός είναι αόρατος μέχρι κάποιος κατάντη να ανοίξει το "προστατευμένο" αρχειακό αντίγραφο χωρίς κωδικό πρόσβασης και να παρατηρήσει ότι απλά λειτουργεί. Η διόρθωση δεν είναι μια διαφορετική κλήση μεθόδου· το PDFiumPas δεν έχει κανένα αντίστοιχο saAddSecurity για να το ζευγαρώσει με το saRemoveSecurity, επειδή η υποκείμενη μηχανή PDFium ποτέ δεν χτίστηκε για να γράφει νέα κρυπτογράφηση, μόνο για να την αφαιρεί. Αν και οι δύο ιδιότητες έχουν σημασία για ένα αρχείο, η κρυπτογράφηση πρέπει να είναι ένα ξεχωριστό βήμα που κατέχετε εσείς, εφαρμοσμένο μετά τα σημάδια συμμόρφωσης, όχι διπλωμένο μέσα στην ίδια κλήση SaveAsPdfA
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.Password := 'open-secret'; // needed to open the source at all
Pdf.FileName := 'signed-encrypted-invoice.pdf';
Pdf.LoadDocument;
Pdf.SaveAsPdfA('invoice-pdfa.pdf', pac2b);
// invoice-pdfa.pdf now declares PDF/A-2b, but SaveAs(saRemoveSecurity)
// ran first inside SaveAsPdfA: the output opens without a password
finally
Pdf.Free;
end;
end;
Τι συμβαίνει όταν υπογράφετε ένα κρυπτογραφημένο PDF με PAdES;
Το PDFiumPas αρνείται εντελώς, αντί να ρίχνει σιωπηλά το αίτημα με τον τρόπο που το κάνει ένας εγχυτής σημαδιών. Τόσο το TPdf.SignPades όσο και το SignPadesToStream δρομολογούνται μέσα από ένα εσωτερικό SignPadesBytes, και το πρώτο πράγμα που κάνει αφού αναλύσει το trailer πηγή είναι να ελέγξει για /Encrypt. Αν η καταχώριση είναι παρούσα, εγείρει EPadesCrypto με το μήνυμα "SignPadesBytes: the source document is encrypted; remove encryption before signing" αντί να προχωρήσει περαιτέρω. Το InjectPadesDssMarkers, η συνάρτηση που ενσωματώνει πιστοποιητικά, απαντήσεις OCSP, και CRL για μακροπρόθεσμη επικύρωση, εφαρμόζει τον ταυτόσημο έλεγχο για τον ταυτόσημο λόγο, με το δικό της μήνυμα: "InjectPadesDssMarkers: the source document is encrypted; remove encryption before embedding DSS validation material"
Το σκεπτικό εδώ είναι αυστηρότερο από το πέρασμα των εγχυτών σημαδιών, και σκόπιμα. Ένα σιωπηλό πέρασμα είναι ασφαλές για μια σφραγίδα PDF/A επειδή η παράλειψή της σας αφήνει με το ίδιο έγκυρο PDF με το οποίο ξεκινήσατε, απλώς χωρίς ετικέτα. Η υπογραφή δεν μπορεί να αποτύχει τόσο ήσυχα: μια υπογραφή που σιωπηλά ποτέ δεν προστέθηκε φαίνεται, σε οποιονδήποτε καλούντα κώδικα που ελέγχει μόνο ένα boolean αποτέλεσμα, ακριβώς σαν μια υπογραφή που προστέθηκε επιτυχώς. Το EPadesCrypto κατάγεται από την κοινή κλάση Exception, οπότε η σύλληψή της είναι κανονικός χειρισμός εξαιρέσεων, όχι μια ειδική σύμβαση ροής ελέγχου που πρέπει να μάθετε
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.Password := 'open-secret';
Pdf.FileName := 'encrypted-contract.pdf';
Pdf.LoadDocument;
try
Pdf.SignPades('encrypted-contract-signed.pdf', 'A1B2C3D4E5F6...');
except
on E: EPadesCrypto do
// E.Message: 'SignPadesBytes: the source document is encrypted;
// remove encryption before signing'
raise Exception.Create('Decrypt the contract before signing: '+ E.Message);
end;
finally
Pdf.Free;
end;
end;
Σειροθέτηση σφραγίδων συμμόρφωσης, υπογραφών, και κρυπτογράφησης
Η πρακτική διόρθωση είναι η σειρά, όχι μια διαφορετική βιβλιοθήκη. Εφαρμόστε πρώτα σημάδια PDF/A, PDF/X, PDF/UA, PDF/E-1, PDF/R-1, ή PDF/VT-1, προσθέστε οποιαδήποτε υπογραφή PAdES στη συνέχεια, και μόνο τότε εκτελέστε όποιο βήμα στη διοχέτευσή σας πράγματι κατέχει την κρυπτογράφηση, είτε αυτό είναι ένας αφιερωμένος γραφέας PDF, μια συσκευή υπογραφής, ή η δική σας υλοποίηση AES. Το επίπεδο επαυξητικής ενημέρωσης του PDFiumPas ταιριάζει φυσικά στη μέση εκείνης της ακολουθίας, προσαρτώντας μικρά, στοχευμένα αντικείμενα πάνω σε ένα αρχείο που κατά τα άλλα είναι ολοκληρωμένο, και η κρυπτογράφηση ανήκει στο τέλος ακριβώς επειδή είναι η μία λειτουργία στην αλυσίδα που το ίδιο το PDFiumPas δεν μπορεί να εκτελέσει ή να αναιρέσει
Τίποτα από αυτά δεν αλλάζει το πώς διαβάζει το PDFiumPas το trailer και τα δεδομένα παραπομπής παύλας από τα οποία εξαρτάται κάθε επαυξητική ενημέρωση, κάτι που είναι η δική του πηγή λεπτότητας μόλις μπουν στην εικόνα ροές xref· η επικύρωση των ροών αντικειμένων και xref ενός PDF καλύπτει πώς εκείνη η ίδια διαδρομή ανάγνωσης trailer χειρίζεται συμπιεσμένες δομές PDF 1.5+. Και μόλις ένα έγγραφο είναι έτοιμο για κάτι ισχυρότερο από μια σφραγίδα συμμόρφωσης, η υπογραφή ενός PDF με υπογραφή PAdES B-B σε Delphi είναι εκεί όπου το SignPades αναλαμβάνει ακριβώς από το σημείο όπου σταματά αυτό το άρθρο
Οι εγχυτές σημαδιών και οι μέθοδοι SignPades που περιγράφονται εδώ διατίθενται ως μέρος του εξαρτήματος PDFium για Delphi και C++Builder, μαζί με την απόδοση και την επιθεώρηση μόνο-ανάγνωσης που παρέχει εγγενώς το PDFium