Τεχνικό Άρθρο

Έλεγχος κρυπτογράφησης και δικαιωμάτων PDF στο Delphi με το PDF Library for Delphi

Μια σημαία δικαιωμάτων δεν είναι μηχανισμός ασφάλειας. Το bit που λέει «απαγορεύεται η αντιγραφή» βρίσκεται μέσα στο ίδιο λεξικό /Encrypt με την κρυπτογραφία, πράγμα που του προσδίδει έναν αέρα επιβολής τον οποίο δεν διαθέτει, και τη στιγμή που αντιμετωπίζετε τα δύο ως ένα πράγμα ο έλεγχός σας αρχίζει να παράγει λανθασμένες απαντήσεις. Το μόνο ερώτημα που αξίζει να τεθεί για ένα PDF δεν είναι «είναι κρυπτογραφημένο;». Είναι πιο συγκεκριμένο και πιο δύσκολο: ποιος αλγόριθμος, ποια αναθεώρηση του security handler, ποιο από τα δύο συνθηματικά ορίστηκε, ποια bits δικαιωμάτων δηλώνονται και ποια μέρη του αρχείου αγγίζει πραγματικά η κρυπτογράφηση. Ένα αρχείο μπορεί να είναι τυπικά κρυπτογραφημένο και πρακτικά ανοιχτό. Μπορεί να αρνείται να διαβαστεί και ωστόσο να αφήνει τα μεταδεδομένα του σε καθαρό κείμενο. Μπορεί να κλειδώνει την εκτύπωση μέσα σε μια σημαία που κάθε πρόγραμμα προβολής είναι ελεύθερο να αγνοήσει. Ο έλεγχος ενός PDF σημαίνει να επιλύετε όλα αυτά ξεχωριστά, και το PDF Library for Delphi, η μηχανή PDF της losLab για Delphi και C++Builder, εκθέτει το καθένα από αυτά τόσο μέσα από ένα επίπεδο API με ακέραιους handles όσο και μέσα από ένα τυποποιημένο επίπεδο κλάσεων

Τι καταγράφει πραγματικά το λεξικό /Encrypt

Το ISO 32000-1 §7.6 ορίζει την ασφάλεια εγγράφου μέσα από μια χούφτα καταχωρίσεων λεξικού, και το PDF Library for Delphi τις αντικατοπτρίζει μία προς μία στην εγγραφή TPDFEncryption. Η έκδοση φίλτρου V και η αναθεώρηση R επιλέγουν την οικογένεια αλγορίθμων. Το Length μεταφέρει το μέγεθος του κλειδιού. Τα bits δικαιωμάτων βρίσκονται στο P, οι συμβολοσειρές επικύρωσης του συνθηματικού κατόχου και του συνθηματικού χρήστη στα O και U (με τα OE και UE να προστίθενται για το AES-256), μια σημαία EncryptMetadata τα συνοδεύει, και τρία ακόμη πεδία ονομάζουν τα crypt filters που εφαρμόζονται σε συμβολοσειρές, ροές και ενσωματωμένα αρχεία αντίστοιχα

Η αξία αυτής της εγγραφής είναι ότι δεν ερμηνεύει τίποτα για λογαριασμό σας. Επιστρέφει το ακατέργαστο λεξικό και σας αφήνει να βγάλετε τα συμπεράσματα, που είναι ακριβώς αυτό που χρειάζεται ένας έλεγχος. Η περίπτωση «καθαρό κείμενο μέσα σε κρυπτογραφημένο» εμφανίζεται στα StringFilterIdentity και StreamFilterIdentity: όταν κάποιο από τα δύο είναι true, τα αντίστοιχα δεδομένα περνούν άθικτα μέσα από το φίλτρο Identity, ανεξάρτητα από το τι αναφέρει η κρυπτογραφημένη κατάσταση του εγγράφου. Ένας σαρωτής που σταματά στο «υπάρχει λεξικό /Encrypt» θα χαρακτηρίσει ένα τέτοιο αρχείο προστατευμένο ενώ οι συμβολοσειρές και οι ροές του βρίσκονται εκτεθειμένες. Η ίδια λεπτομέρεια διέπει τα μεταδεδομένα. Όταν το EncryptMetadata είναι false, το πακέτο XMP παραμένει αναγνώσιμο από οποιονδήποτε indexer ενώ το περιεχόμενο των σελίδων όχι, κάτι που αξίζει να το γνωρίζετε τη στιγμή που οι κανόνες δρομολόγησής σας βασίζονται σε ένα πεδίο τίτλου ή συντάκτη

Διάγραμμα του PDF Library for Delphi με τα πεδία του λεξικού /Encrypt του PDF αντιστοιχισμένα σε ιδιότητες ελέγχου TPDFEncryption, περιλαμβανομένων των παγίδων των φίλτρων κρυπτογράφησης Identity
Κάθε εγγραφή του /Encrypt αντιστοιχίζεται σε πεδίο του TPDFEncryption, και οι σημαίες του φίλτρου Identity αποκαλύπτουν ποιες συμβολοσειρές, ροές ή μεταδεδομένα παραμένουν αναγνώσιμα ανεξάρτητα από την κατάσταση κρυπτογράφησης

Ένας σύντομος έλεγχος ασφάλειας με το επίπεδο API

Για τις περισσότερες ροές επεξεργασίας, τέσσερις επίπεδες κλήσεις απαντούν στα καθημερινά ερωτήματα. Η LoadFromFile επιστρέφει 1 σε επιτυχία, και μόλις ανοίξει το έγγραφο οι επιθεωρητές κρυπτογράφησης αναφέρουν με βάση την αποκρυπτογραφημένη κατάστασή του:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // αποκρυπτογραφημένο / κρυπτογραφημένο / άγνωστο
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // οικογένεια RC4 έναντι AES
    Writeln('strength  : ', PDF.EncryptionStrength);   // κλάση μήκους κλειδιού
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

Η CheckPassword έχει μεγαλύτερη σημασία απ' όση υπονοεί η μονογραμμική υπογραφή της. Το PDF ορίζει δύο συνθηματικά με άνιση ισχύ. Το συνθηματικό χρήστη απαιτείται για να ανοίξει καν το αρχείο. Το συνθηματικό κατόχου παραχωρεί πλήρη δικαιώματα και υπερισχύει κάθε bit δικαιωμάτων. Τα bytes στον δίσκο είναι πανομοιότυπα και στις δύο περιπτώσεις, αλλά μια συνεδρία που άνοιξε με το συνθηματικό κατόχου μπορεί να κάνει πράγματα που δεν μπορεί μια συνεδρία με συνθηματικό χρήστη, οπότε ένας έλεγχος που δεν καταγράφει ποιο διαπιστευτήριο παρουσιάστηκε καταγράφει τη μισή αλήθεια. Το επίπεδο κλάσεων κάνει τη διάκριση διερωτήσιμη. Οι TPDFDocument.HasUserPassword και HasOwnerPassword αναφέρουν τι απαιτεί το αρχείο, ενώ οι IsUserPassword και IsOwnerPassword αναφέρουν ποιο συνθηματικό άνοιξε πραγματικά την τρέχουσα συνεδρία. Καταγράψτε αυτό το γεγονός. Ποτέ μην καταγράφετε τις ίδιες τις τιμές των συνθηματικών

Η κλίμακα Strength, όπου το «AES-256» σημαίνει δύο πράγματα

Οι επίπεδες συναρτήσεις Encrypt και EncryptFile δέχονται μια ακέραια τιμή Strength με πέντε ουσιαστικές τιμές: 0 για RC4 40 bit, 1 για RC4 128 bit, 2 για AES 128 bit αναγνώσιμο από τον Acrobat 7, 3 για AES 256 bit όπως εισήχθη με τον Acrobat 9, και 4 για AES 256 bit όπως απαιτείται από τον Acrobat X και τους μεταγενέστερους

Το ενδιαφέρον είναι ότι το 3 και το 4 φέρουν και τα δύο την ετικέτα AES-256 και δεν είναι το ίδιο σχήμα. Το Strength 3 αντιστοιχεί στην αναθεώρηση 5 του security handler, έναν ενδιάμεσο σχεδιασμό που κυκλοφόρησε ο Acrobat 9 και το ISO ποτέ δεν υιοθέτησε. Το Strength 4 αντιστοιχεί στην αναθεώρηση 6, της οποίας η συνάρτηση παραγωγής κλειδιού θωρακίστηκε και τυποποιήθηκε στο ISO 32000-2. Για ένα έγγραφο που δημιουργείτε σήμερα δεν υπάρχει λόγος να προτιμήσετε το 3 από το 4. Για έναν έλεγχο η διαφορά είναι καθοριστική: μια πολιτική που λέει «AES-256 κατά ISO 32000-2» ικανοποιείται μόνο από την R6, και ένα αρχείο R5 που αυτοαποκαλείται AES-256 αποτυγχάνει σε αυτήν την πολιτική ενώ περνά έναν αφελή έλεγχο ισχύος. Το επίπεδο κλάσεων κρατά τα δύο χωριστά ονομαστικά, esAES256Bit για την R5 έναντι esAES256BitAcroX για την R6, και η ιδιότητα EncryptionAcroX απαντά στο ερώτημα της αναθεώρησης με ένα μοναδικό boolean

Κλίμακα ισχύος κρυπτογράφησης PDF από RC4 40 bit σε AES-256 αναθεώρηση 5 έναντι αναθεώρησης 6 για ελέγχους Delphi
Οι ισχύς 3 και 4 και οι δύο αποκαλούνται AES-256, αλλά μόνο η αναθεώρηση 6 ικανοποιεί πολιτική ISO 32000-2, οπότε οι έλεγχοι πρέπει να καταγράφουν την αναθεώρηση του handler και όχι μόνο την ετικέτα

Τα bits δικαιωμάτων και τα ψιλά γράμματα του μήκους κλειδιού

Η EncodePermissions συσκευάζει οκτώ σημαίες στον ακέραιο που περιμένουν οι Encrypt και EncryptFile. Η εκτύπωση, η αντιγραφή, η αλλαγή και η προσθήκη σημειώσεων συγκροτούν το βασικό σύνολο· η συμπλήρωση πεδίων, η αντιγραφή για προσβασιμότητα, η συναρμολόγηση και η εκτύπωση πλήρους ποιότητας συγκροτούν το εκτεταμένο σύνολο. Τα ψιλά γράμματα, τα οποία δηλώνει ρητά το ίδιο το demo κρυπτογράφησης της βιβλιοθήκης, είναι ότι οι τέσσερις εκτεταμένες σημαίες τίθενται σε ισχύ μόνο σε ισχύ 128 bit και άνω. Η σημαία εκτύπωσης πλήρους ποιότητας υπάγεται στον ίδιο κανόνα: καθαρίστε την για να επιβάλετε εκτύπωση χαμηλής ανάλυσης και ένα έγγραφο 40 bit θα σας αγνοήσει, επειδή και αυτός ο υποβιβασμός απαιτεί κρυπτογράφηση 128 bit ή ισχυρότερη. Κωδικοποιήστε μια πολιτική «μόνο εκτύπωση χαμηλής ανάλυσης» σε ένα αρχείο 40 bit και κάθε πρόγραμμα προβολής θα εκτυπώσει έτσι κι αλλιώς σε πλήρη ποιότητα

Το βαθύτερο ερώτημα είναι ποιος επιβάλλει οποιοδήποτε από αυτά τα bits, και η απάντηση είναι κανείς που να μπορείτε να εμπιστευτείτε. Τα δικαιώματα είναι οδηγίες προς τα συμμορφούμενα προγράμματα ανάγνωσης, όχι κρυπτογραφικοί περιορισμοί. Το κλειδί αποκρυπτογράφησης είναι πανομοιότυπο είτε επιτρέπεται είτε απαγορεύεται η αντιγραφή, οπότε ένα κλειδωμένο σύνολο δικαιωμάτων απλώς κρατά τα έντιμα προγράμματα προβολής έντιμα. Ένα πρόγραμμα ανάγνωσης που επιλέγει να αγνοήσει τα bits δεν αντιμετωπίζει κανένα απολύτως κρυπτογραφικό εμπόδιο. Αν η υποχρέωση είναι να αποτραπεί η εξαγωγή και όχι απλώς να αποθαρρυνθεί, το αρχείο χρειάζεται συνθηματικό χρήστη και η ροή εργασίας χρειάζεται ελέγχους σε επίπεδο διαδικασίας γύρω από αυτό, και μια αναφορά ελέγχου θα πρέπει να κατονομάζει σε ποιο από τα δύο καθεστώτα υπάγεται πραγματικά κάθε αρχείο αντί να αντιμετωπίζει μια σημαία δικαιωμάτων ως κλειδαριά

Ορισμός πολιτικής και απόδειξη ότι εφαρμόστηκε

Η εφαρμογή κρυπτογράφησης σε υπάρχοντα αρχεία δεν απαιτεί τη φόρτωσή τους στο δέντρο αντικειμένων. Η EncryptFile επεξεργάζεται είσοδο προς έξοδο σε μία κλήση, και ο βρόχος ελέγχου ανοίγει ξανά το αποτέλεσμα για να επιβεβαιώσει τι κατέληξε στον δίσκο. Το demo κρυπτογράφησης που συνοδεύει τη βιβλιοθήκη ακολουθεί το ίδιο σχήμα εγγραφής και επανανάγνωσης:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // επιτρέπεται η εκτύπωση· άρνηση αντιγραφής/αλλαγής/σημειώσεων
                            0, 0, 0, 1));  // εκτεταμένο σύνολο: μόνο εκτύπωση πλήρους ποιότητας
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Οι ομάδες που δουλεύουν στο επίπεδο εγγράφου παίρνουν την ίδια λειτουργία με τυποποιημένα σύνολα στη θέση της συσκευασίας bits, κάτι που επιβιώνει από έναν έλεγχο κώδικα με πολύ λιγότερο στραβοκοίταγμα:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

Είτε έτσι είτε αλλιώς, το βήμα της επανανάγνωσης δεν είναι προαιρετική τελετουργία. Πιάνει τα σφάλματα ανάπτυξης που αλλιώς εμφανίζονται μήνες αργότερα στο μηχάνημα ενός πελάτη: μια παλιά έκδοση βιβλιοθήκης που υποβαθμίζει σιωπηλά τη ζητούμενη ισχύ, μια διαδρομή εξόδου που δεν γράφτηκε ποτέ επειδή ο κατάλογος ήταν μόνο για ανάγνωση, έναν ακέραιο δικαιωμάτων του οποίου τα ορίσματα μπήκαν με λάθος σειρά. Και τα τρία περνούν ένα τοπικό smoke test και αποτυγχάνουν στο πεδίο, και το ξανάνοιγμα της εξόδου μετατρέπει το καθένα τους σε εξαίρεση που βλέπετε κατά την ίδια εκτέλεση που δημιούργησε το αρχείο. Η GetEncryptionFingerprint επιστρέφει μια συμπαγή τιμή που μπορείτε να αποθηκεύσετε μαζί με την εγγραφή της εργασίας, ώστε μια μεταγενέστερη σύγκριση να μπορεί να πει αν δύο έξοδοι μοιράζονται την ίδια διαμόρφωση κρυπτογράφησης χωρίς να ανοίξετε ξανά καμία από τις δύο

Ψευδώς θετικά ελέγχου που αξίζει να προβλέψετε στον κώδικα

Μερικά μοτίβα οδηγούν αξιόπιστα τους σαρωτές ασφάλειας σε λάθος συμπέρασμα, και το καθένα προκύπτει από τη σύμπτυξη ενός πολυμερούς ερωτήματος σε μια απάντηση ναι ή όχι. Το crypt filter Identity είναι το καθαρότερο παράδειγμα. Υπάρχει λεξικό /Encrypt, το αρχείο αναφέρεται ως κρυπτογραφημένο, κι όμως οι συμβολοσειρές και οι ροές περνούν αναλλοίωτες μέσα από το φίλτρο Identity, οπότε το πραγματικό περιεχόμενο είναι καθαρό κείμενο. Η λύση είναι να διαβάζετε τα StringFilterIdentity και StreamFilterIdentity προτού χαρακτηρίσετε οτιδήποτε προστατευμένο

Ο διαχωρισμός των μεταδεδομένων είναι πιο λεπτός. Το EncryptMetadata μπορεί να διαφωνεί με το υπόλοιπο έγγραφο και προς τις δύο κατευθύνσεις, αφήνοντας ένα κρυπτογραφημένο αρχείο με αναγνώσιμο πακέτο XMP ή, σπανιότερα, το αντίστροφο. Το «το αρχείο είναι κρυπτογραφημένο» δεν λέει τίποτα για το αν είναι και τα μεταδεδομένα του, κάτι που μετράει τη στιγμή που ένας indexer ή ένας κανόνας δρομολόγησης πάει να πιάσει τον τίτλο. Τα ενσωματωμένα αρχεία προσθέτουν έναν τρίτο άξονα: το PDF επιτρέπει ένα ειδικό crypt filter μόνο για τα συνημμένα, οπότε τα συνημμένα μπορεί να είναι το μοναδικό κρυπτογραφημένο μέρος ενός κατά τα άλλα ανοιχτού εγγράφου, ή το μοναδικό τμήμα σε καθαρό κείμενο ενός κρυπτογραφημένου. Καταγράψτε τις τρεις αναθέσεις φίλτρων ως ξεχωριστά πεδία για συμβολοσειρές, ροές και ενσωματωμένα αρχεία, και καμία από αυτές τις παγίδες δεν μπορεί να σας πιάσει. Αποθηκεύστε ένα μόνο boolean και η λανθασμένη κρίση είναι απλώς θέμα χρόνου

PDF Library for Delphi: Ροή ελέγχου που εξετάζει StringFilterIdentity, StreamFilterIdentity, EncryptMetadata και το φίλτρο κρυπτογράφησης ενσωματωμένων αρχείων πριν χαρακτηρίσει ένα PDF προστατευμένο
Τέσσερις ανεξάρτητοι άξονες αποφασίζουν αν ένα φαινομενικά κρυπτογραφημένο αρχείο είναι πραγματικά σφραγισμένο, και η συμπίεσή τους σε ένα boolean καταλήγει τελικά να ταξινομεί λανθασμένα ένα αρχείο

Αφαίρεση της κρυπτογράφησης και επιλογή της για νέα αρχεία

Ένας έλεγχος καταλήγει συχνά σε απόφαση αφαίρεσης της προστασίας, και η μηχανική δεν είναι εκεί το εμπόδιο. Η DecryptFile(InputFileName, OutputFileName, Password) γράφει ένα αποκρυπτογραφημένο αντίγραφο χωρίς πλήρη φόρτωση, και η Decrypt του φορτωμένου εγγράφου κάνει το ίδιο στη μνήμη όταν ένα αρχείο είναι ήδη ανοιχτό. Και οι δύο απαιτούν έγκυρο συνθηματικό· καμία δεν παρακάμπτει την κρυπτογραφία. Η πραγματική πύλη είναι η πολιτική και όχι ο κώδικας, οπότε φροντίστε οι κανόνες παραλαβής σας να δηλώνουν ξεκάθαρα πότε επιτρέπεται η αφαίρεση και να καταγράφουν την κλάση συνθηματικού που την ενέκρινε, επειδή το ίδιο το τεχνικό βήμα δεν αφήνει κανένα ίχνος

Η επιλογή για νέα έξοδο είναι στενότερη απ' όσο υπονοούν οι πέντε τιμές Strength. Χρησιμοποιήστε Strength 4, AES-256 αναθεώρηση 6, εκτός αν πρέπει να ανοίγετε αρχεία σε προγράμματα προβολής παλαιότερα από τον Acrobat X. Το Strength 2, AES-128, είναι το πραγματιστικό κατώφλι για έναν γερασμένο στόλο προγραμμάτων προβολής που δεν μπορεί να αναβαθμιστεί. Οι επιλογές RC4 στο 0 και στο 1 υπάρχουν για να μπορείτε να διαβάζετε και να ελέγχετε ιστορικά αρχεία, όχι για να παράγετε κάτι νέο με αυτές· το να καταφεύγετε σε αυτές σε έναν σχεδιασμό του 2026 είναι ένδειξη ότι κάποια απαίτηση ανάντη έχει παλιώσει

Η κατάσταση κρυπτογράφησης τροφοδοτεί απευθείας τις αποφάσεις υπογραφής, αφού ένας πάγκος εργασίας που επικυρώνει και υπογράφει έγγραφα χρειάζεται την ίδια πειθαρχία επανανάγνωσης στην οποία στηρίζεται αυτός ο έλεγχος. Το έδαφος αυτό καλύπτεται στο άρθρο για τον πάγκο εργασίας συμμόρφωσης και υπογραφής. Όταν μια παρτίδα εφαρμόζει την EncryptFile σε χιλιάδες μεγάλα έγγραφα, ο οδηγός άμεσης πρόσβασης για μεγάλα PDF δείχνει πώς να κρατήσετε τη μνήμη σταθερή όσο τρέχει. Η πλήρης αναφορά του API κρυπτογράφησης βρίσκεται στη σελίδα προϊόντος PDF Library for Delphi