Η πραγματική απόκρυψη (redaction) PDF στο Delphi σημαίνει τη διαγραφή των υποκείμενων bytes, και όχι την απλή κάλυψή τους με χρώμα. Η losLab PDF Library τραβάει αυτή τη γραμμή με δύο API: το RedactRegion επεξεργάζεται τη ροή περιεχομένου (content stream) της σελίδας, έτσι ώστε το αφαιρεθέν κείμενο, τα διανύσματα και οι εικόνες να έχουν φυσικά χαθεί, και το SanitizeDocument καθαρίζει τα μεταδεδομένα, τα σενάρια (scripts) και τις ενέργειες (actions) όπου επίσης κρύβονται αποκρυμμένες πληροφορίες. Εάν κάνετε λάθος σε οποιοδήποτε από τα δύο μέρη, παραδίδετε ένα αρχείο που φαίνεται λογοκριμένο στην οθόνη, αλλά αποκαλύπτει το μυστικό σε οποιονδήποτε αντιγράψει το κείμενο ή ανοίξει τις Ιδιότητες Εγγράφου
Αυτή η αποτυχία αποτελεί δημόσιο γεγονός. Εφημερίδες, δικαστήρια και κυβερνητικοί οργανισμοί έχουν δημοσιεύσει κατά καιρούς PDF με ένα μαύρο ορθογώνιο τοποθετημένο πάνω από ένα όνομα ή έναν αριθμό, μόνο και μόνο για να μπορέσει ένας αναγνώστης να επιλέξει το κείμενο από κάτω, να το αντιγράψει και να επικολλήσει τη δήθεν αποκρυμμένη συμβολοσειρά σε κοινή θέα. Το ορθογώνιο ήταν απλώς ένα σχέδιο. Το κείμενο βρισκόταν ακόμα εκεί, στη ροή περιεχομένου ένα επίπεδο κάτω από το χρώμα, πλήρως άθικτο και επιλέξιμο. Η losLab PDF Library, η μηχανή PDF για Delphi και C++Builder που εκτίθεται μέσω της κλάσης TPDFlib, αντιμετωπίζει αυτή τη διάκριση ως την ουσία της απόκρυψης και όχι ως εκ των υστέρων σκέψη
Γιατί η σχεδίαση ενός μαύρου πλαισίου πάνω από το κείμενο PDF δεν είναι πραγματική απόκρυψη;
Η losLab PDF Library εκθέτει το MaskRect για τις περιπτώσεις όπου η κάλυψη είναι αυτό που πραγματικά θέλετε: ένα υδατογράφημα (watermark), ένα οπτικό blackout, μια σφραγίδα δοκιμίου. Το MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) προσαρτά ένα ορθογώνιο στη ροή περιεχομένου της σελίδας και το σχεδιάζει με τον τελεστή κατασκευής διαδρομής re, ακολουθούμενο από τον τελεστή γεμίσματος f (ISO 32000-1 §8.5). Κάθε byte που βρισκόταν ήδη στη σελίδα — οι γλύφοι του ονόματος που καλύψατε, οι διανυσματικές γραμμές, η ενσωματωμένη εικόνα — παραμένει ακριβώς εκεί που ήταν, μια εντολή νωρίτερα στην ίδια ροή. Ένα συμβατό πρόγραμμα προβολής σχεδιάζει το μαύρο πλαίσιο τελευταίο, οπότε αυτό υπερισχύει οπτικά. Η κάλυψη δεν αλλάζει τίποτα από κάτω
Οποιοσδήποτε μπορεί να ανακτήσει το αρχικό περιεχόμενο με τρεις τρόπους: να επιλέξει και να αντιγράψει το κείμενο απευθείας μέσα από το αδιαφανές πλαίσιο, να εκτελέσει έναν εξαγωγέα κειμένου στη σελίδα ή να διαγράψει το ορθογώνιο κάλυψης από τη ροή περιεχομένου. Το MaskRect είναι ειλικρινές ως προς αυτό με το ίδιο του το όνομα — καλύπτει, δεν αφαιρεί. Το αποτέλεσμα μοιάζει με ένα αυτοκόλλητο πάνω από μια λέξη που μπορείτε ακόμα να διαβάσετε ανασηκώνοντας το αυτοκόλλητο
Πώς το RedactRegion διαγράφει περιεχόμενο σε επίπεδο εντολών
Το RedactRegion(Page, Left, Top, Width, Height, Options) λειτουργεί ένα επίπεδο κάτω από το χρώμα. Η losLab PDF Library αναλύει κάθε επίπεδο περιεχομένου της σελίδας σε ένα TPDFContentProgram, διατρέχει τη λίστα εντολών με έναν αναλυτή παραкоλούθησης CTM (FindInstructionsInRect στο μοντέλο περιεχομένου) και επισημαίνει κάθε τελεστή εμφάνισης κειμένου, σχεδίασης διαδρομής και εικόνας Do του οποίου η σχεδιασμένη περιοχή τέμνει το ορθογώνιό σας. Αυτές οι εντολές αφαιρούνται με το TPDFContentProgram.Delete, και το επεξεργασμένο επίπεδο εγγράφεται ξανά με το WritePageContentLayer. Τα bytes δεν γλιστρούν πίσω από ένα κάλυμμα — εγκαταλείπουν τη ροή. Ένας εξαγωγέας κειμένου που εκτελείται στη συνέχεια δεν βρίσκει τίποτα, επειδή δεν υπάρχει τίποτα για να βρει
Δύο λεπτομέρειες υλοποίησης αξίζει να έχετε κατά νου. Το RedactRegion λαμβάνει ένα ορθογώνιο πάνω-αριστερά στην τρέχουσα αρχή και μονάδες και το μετατρέπει εσωτερικά στο κάτω-αριστερά user space που χρησιμοποιεί η ροή περιεχομένου, οπότε ορίζετε την περιοχή με τον τρόπο που τη βλέπετε στη σελίδα. Όταν ένα ορθογώνιο καλύπτει αρκετές εντολές, το RedactRegion τις διαγράφει σε φθίνουσα σειρά ευρετηρίου, επειδή η αφαίρεση της εντολής 4 πριν από την εντολή 7 θα μετατόπιζε κάθε μεταγενέστερο ευρετήριο και θα κατέστρεφε το εύρος. Περάστε το Options = 0 και η losLab PDF Library τοποθετεί επίσης ένα συμπαγές μαύρο πλαίσιο πάνω από την καθαρισμένη περιοχή ως ορατό δείκτη — αλλά αυτός ο δείκτης είναι κοσμητικός, εφαρμόζεται αφού το πραγματικό περιεχόμενο έχει ήδη αφαιρεθεί
Στην πράξη σπάνια κωδικοποιείτε σκληρά (hard-code) το ορθογώνιο. Αναζητάτε στη σελίδα την ευαίσθητη συμβολοσειρά και διαβάζετε το πλαίσιο οριοθέτησής της (bounding box) — η ίδια αναζήτηση σελίδας και απαρίθμηση στοιχείων που καλύπτεται στο εντοπισμός κειμένου και στοιχείων σελίδας στο Delphi — και στη συνέχεια παραδίδετε αυτό το ορθογώνιο στο RedactRegion:
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', '') = 1 then
begin
// Διαγράψτε οτιδήποτε έχει σχεδιαστεί μέσα σε ένα πλαίσιο 220 x 14 pt στη σελίδα 1
// Οι συντεταγμένες είναι πάνω-αριστερά, στην τρέχουσα αρχή και μονάδες
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // Options=0 τοποθετεί έναν μαύρο δείκτη
PDF.SaveToFile('contract-redacted.pdf');
end;
finally
PDF.Free;
end;
end;
Πού εξακολουθούν να κρύβονται αποκρυμμένες πληροφορίες σε ένα PDF;
Η απόκρυψη που σταματά στο ορατό επίπεδο αφήνει ίχνη, επειδή ένα PDF αποθηκεύει πληροφορίες σε μέρη που ένας αναγνώστης δεν κοιτάζει ποτέ. Η losLab PDF Library ομαδοποιεί τις διαρροές σε κανάλια που αξίζει να αναφερθούν. Το Document Information Dictionary και η ροή μεταδεδομένων XMP μεταφέρουν συνήθως μια συμβολοσειρά Author, Producer ή Keywords που ονομάζει το ίδιο το πρόσωπο ή την υπόθεση που αφαιρέσατε από το σώμα του κειμένου. Το JavaScript σε επίπεδο εγγράφου και το OpenAction του καταλόγου μπορούν να εκτελεστούν κατά το άνοιγμα και να αποκτήσουν πρόσβαση σε δεδομένα που νομίζατε ότι είχαν χαθεί. Κάθε σχολιασμός φέρει μια προαιρετική ενέργεια /A, και ένας σχολιασμός ο ίδιος — μια αυτοκόλλητη σημείωση, ένα παλιό πεδίο φόρμας, ένας σύνδεσμος — μπορεί να βρίσκεται απευθείας πάνω από το αποκρυμμένο σημείο με το δικό του αντίγραφο του κειμένου. Τίποτα από αυτά δεν επηρεάζεται από την κρυπτογράφηση. Ένα κρυπτογραφημένο αρχείο με πακέτο XMP απλού κειμένου εξακολουθεί να διαρρέει τον τίτλο του σε οποιονδήποτε ευρετηριαστή (indexer), ένα κενό που αξίζει να ελέγξετε την κρυπτογράφηση και τα δικαιώματα από μόνο του. Η αμυντική εξυγίανση (sanitization) πρέπει να επισκεφθεί καθένα από αυτά τα κρυφά σημεία, όχι μόνο τη σελίδα που κοιτούσατε
Καθαρισμός του εγγράφου με το SanitizeDocument
Η losLab PDF Library αντιμετωπίζει αυτό το εύρος διαρροών με το SanitizeDocument(Flags), μια κλήση ενορχήστρωσης που εκτελεί ένα σύνολο ανεξάρτητων αφαιρέσεων που επιλέγονται από ένα bitmask και επιστρέφει τον αριθμό των αφαιρέσεων που εφάρμοσε. Οι σημαίες (flags) είναι συνδυάσιμες. Το SANITIZE_JAVASCRIPT αφαιρεί κάθε πακέτο JavaScript σε επίπεδο εγγράφου και οποιοδήποτε σενάριο OpenAction. Το SANITIZE_ACTIONS αφαιρεί το λεξικό ενεργειών /A από κάθε σχολιασμό, μέσω του νέου TPDFAnnots.RemoveAnnotAction, και καθαρίζει το OpenAction του καταλόγου. Το SANITIZE_METADATA σβήνει τα Author, Subject, Keywords, Creator και Producer τόσο στο Document Information Dictionary (ISO 32000-1 §14.3.3) όσο και στη ροή μεταδεδομένων XMP (§14.3.2). Το SANITIZE_ANNOTATIONS διαγράφει κάθε σχολιασμό σε ολόκληρο το έγγραφο, και το SANITIZE_OPEN_ACTION αφαιρεί την καταχώριση /OpenAction του καταλόγου. Το SANITIZE_ALL είναι η δυαδική ένωση και των πέντε
Οι συμβάσεις ευρετηρίου μέσα σε αυτές τις αφαιρέσεις είναι ακριβώς το είδος των πραγμάτων που σπάνε σιωπηλά έναν χειροκίνητο βρόχο, γεγονός που εξηγεί σε μεγάλο βαθμό γιατί το SanitizeDocument υπάρχει ως μία κλήση. Το GlobalJavaScriptPackageName βασίζεται στο 0, και το RemoveGlobalJavaScript συρρικνώνει τη λίστα καθώς προχωρά, οπότε η αφαίσεση JavaScript διαβάζει πάντα το ευρετήριο πακέτου 0 και αφαιρεί μέχρι ο αριθμός να φτάσει στο μηδέν. Το DeleteAnnotation, αντίθετα, βασίζεται στο 1. Το AnnotationCount αναφέρει στοιχεία για την τρέχουσα επιλεγμένη σελίδα, οπότε η αφαίρεση σχολιασμών επιλέγει κάθε σελίδα με τη σειρά πριν μετρήσει. Η losLab PDF Library χειρίζεται σωστά αυτά τα όρια εσωτερικά, οπότε περνάτε ένα σύνολο σημαιών και λαμβάνετε έναν αριθμό πίσω, αντί να αναπαράγετε πέντε διαφορετικούς κανόνες επανάληψης
var
Applied: Integer;
begin
// Αφαιρέστε μεταδεδομένα, σενάρια και ενέργειες αλλά διατηρήστε τους σχολιασμούς:
Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);
// Ή καθαρίστε κάθε κρυφό κανάλι, συμπεριλαμβανομένων των σχολιασμών:
Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;
Η πλήρης ροή εργασίας απόκρυψης (redaction) και εξυγίανσης (sanitize)
Συνδυάστε τα δύο και η πλήρης διαδικασία είναι σύντομη. Η losLab PDF Library αφαιρεί το ορατό περιεχόμενο με το RedactRegion, σελίδα προς σελίδα, και στη συνέχεια καθαρίζει κάθε κρυφό κανάλι με το SanitizeDocument(SANITIZE_ALL) προτού γραφτεί το αρχείο. Επειδή η απόκρυψη εκτελείται σχεδόν πάντα σε έγγραφα που έφτασαν εκτός του ελέγχου σας, αξίζει να τα φορτώσετε μέσω μιας θωρακισμένης διαδρομής — την ίδια αμυντική στάση που περιγράφεται στο ασφαλής ανάλυση μη έμπιστων PDF — έτσι ώστε μια κακοσχηματισμένη είσοδος να αποτυγχάνει καθαρά αντί για τη μέση της διαδικασίας απόκρυψης
var
PDF: TPDFlib;
Applied: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('incoming.pdf', '') = 1 then
begin
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // διαγραφή της ευαίσθητης γραμμής
Applied := PDF.SanitizeDocument(SANITIZE_ALL); // καθαρισμός μεταδεδομένων, JS, ενεργειών, σχολιασμών
PDF.SaveToFile('published.pdf');
end;
finally
PDF.Free;
end;
end;
Πού σταματά η απόκρυψη στη ροή περιεχομένου
Το RedactRegion λειτουργεί στη ροή περιεχομένου, η οποία είναι ακριβώς το σημείο όπου ζει το ψηφιακά δημιουργημένο κείμενο και ακριβώς εκεί όπου δεν ζει το σαρωμένο κείμενο. Όταν μια σελίδα είναι σαρωμένη εικόνα, οι λέξεις είναι pixel μέσα σε ένα image XObject και όχι τελεστές κειμένου, και η losLab PDF Library δεν μπορεί να αποκόψει ένα όνομα από τη μέση ενός raster με τον τρόπο που διαγράφει έναν γλύφο. Ο χειρισμός εικόνων είναι σκόπιμα συντηρητικός: οποιαδήποτε εικόνα της οποίας η τοποθέτηση τέμνει το ορθογώνιο αφαιρείται ολόκληρη, οπότε ένα πλαίσιο απόκρυψης σε μια σαρωμένη σελίδα παίρνει ολόκληρη τη σάρωση μαζί του και όχι απλώς ένα τμήμα της. Η απόκρυψη μιας περιοχής μέσα σε μια εικόνα σημαίνει εικονοποίηση (rasterizing), σχεδίαση και εκ νέου ενσωμάτωση σε επίπεδο pixel, πράγμα που αποτελεί διαφορετική λειτουργία από τη διαγραφή ροής περιεχομένου. Η ίδια προσοχή ισχύει και για ένα επίπεδο κειμένου OCR πάνω από μια σαρωμένη σελίδα: το RedactRegion αφαιρεί τους τελεστές κειμένου που μπορεί να δει, αλλά η εικόνα από κάτω παραμένει ανέπαφη εκτός εάν το image XObject της τέμνει επίσης το πλαίσιο
Δύο λεπτομέρειες διατηρούν τα αποτελέσματα αξιόπιστα. Η δοκιμή τομής προσεγγίζει το πλάτος κάθε τμήματος κειμένου από τον αριθμό των χαρακτήρων του και το μέγεθος της γραμματοσειράς του αντί να μετράει ακριβείς μετρικές γλύφων, οπότε μια πολύ σύντομη λέξη γραμμένη σε αναλογική γραμματοσειρά μπορεί να γλιστρήσει μέσα από ένα στενά σχεδιασμένο ορθογώνιο — δώστε στο πλαίσιο ένα μικρό περιθώριο. Η διαγραφή γίνεται επίσης σε επίπεδο εντολών (instruction-granular): το RedactRegion αφαιρεί ολόκληρους τελεστές εμφάνισης κειμένου, οπότε ένα ορθογώνιο που αποκόπτει μόνο ένα μέρος ενός τμήματος μπορεί να συμπεριλάβει γειτονικούς χαρακτήρες που μοιράζονταν τον ίδιο τελεστή. Κανένα από τα δύο όρια δεν αποτελεί λόγο δυσπιστίας προς τον μηχανισμό, αλλά απλώς λόγο για να οριοθετείτε το ορθογώνιο προσεκτικά και να ελέγχετε το αποτέλεσμα
Η πειθαρχία που κλείνει κάθε ροή εργασίας απόκρυψης είναι η επαλήθευση, και όχι η εμπιστοσύνη. Αποκρύψτε την περιοχή, εξυγιάνετε το έγγραφο, αποθηκεύστε σε νέο αρχείο, ανοίξτε το ξανά και προσπαθήστε να ανακτήσετε τόσο το κείμενο που αφαιρέσατε όσο και τα μεταδεδομένα που καθαρίσατε. Όταν και τα δύο επιστρέφουν κενά, η απόκρυψη πέτυχε. Τα API RedactRegion και SanitizeDocument που εμφανίζονται εδώ παρέχονται με τη losLab PDF Library για Delphi και C++Builder, μαζί με την πλήρη αναφορά μοντέλου περιεχομένου και εξυγίανσης