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

Υψηλής Ταχύτητας Κρυπτογράφηση AES-256 PDF για Ογκώδη Έγγραφα

Η κρυπτογράφηση (Encrypting) ενός PDF μεγέθους 2 GB ακούγεται σαν πρόβλημα ροής (streaming): ανοίξτε το αρχείο, σπρώξτε δύο gigabytes μέσα από τον αλγόριθμο AES-256, και γράψτε το αποτέλεσμα. Αυτό το νοητικό μοντέλο (mental model) είναι λάθος με έναν τρόπο που καθορίζει (decides) ολόκληρο τον προϋπολογισμό της απόδοσης (performance budget). Το πρότυπο ISO 32000-1 §7.6 ορίζει το επίπεδο (granularity) κρυπτογράφησης του PDF στο μεμονωμένο αντικείμενο (individual object) — κάθε ροή (stream) και κάθε συμβολοσειρά (string) κρυπτογραφείται ξεχωριστά, καθεμία με το δικό της διάνυσμα αρχικοποίησης (initialization vector / IV) και το δικό της γέμισμα (padding). Ένα σαρωμένο (scanned) αρχείο 2 GB με 500.000 αντικείμενα μεταφράζεται σε 500.000 μικρές πράξεις CBC (Cipher Block Chaining), όχι σε ένα μεγάλο πέρασμα (pass), και σε αυτή την κλίμακα το πάγιο (fixed) κόστος γύρω από κάθε πράξη (operation) έχει μεγαλύτερη σημασία από την ίδια την αριθμητική (arithmetic) του AES

Αυτό το άρθρο αφορά αυτό το πάγιο κόστος (fixed cost): πού πηγαίνει ο χρόνος όταν ο κώδικας του Delphi εφαρμόζει AES-256 σε πολύ μεγάλα έγγραφα, και πώς να τον πάρετε πίσω (get it back). Για το κομμάτι της ρύθμισης (setup) — κωδικοί πρόσβασης, σημαίες δικαιωμάτων (permission flags), η διαφορά στην κλήση συμβατότητας (compatibility call) μεταξύ αναθεώρησης (revision) 5 και 6 — δείτε το συνοδευτικό άρθρο σχετικά με τη ρύθμιση κρυπτογράφησης AES-256 στο HotPDF· τίποτα από αυτά δεν επαναλαμβάνεται εδώ

Μισό εκατομμύριο πράξεις CBC, όχι ένα πέρασμα (pass)

Ο σκελετός (skeleton) του αρχείου παραμένει σε απλό κείμενο (plaintext). Οι πίνακες διασταυρούμενων αναφορών (Cross-reference tables), οι αριθμοί αντικειμένων, τα κλειδιά των λεξικών (dictionary keys), το δέντρο σελίδων (page tree): τίποτα από αυτά δεν είναι κρυπτογραφημένο, κι έτσι μπορεί ένας αναγνώστης (reader) να εντοπίζει τα αντικείμενα πριν καν επικυρώσει (validated) έναν κωδικό πρόσβασης. Αυτό που κρυπτογραφεί το πρότυπο (standard) είναι το περιεχόμενο — δεδομένα ροής (stream data) όπως περιγραφές σελίδων, εικόνες, γραμματοσειρές (fonts), και συνημμένα (attachments), καθώς και συμβολοσειρές όπως τιμές μεταδεδομένων (metadata) και κείμενα σημειώσεων (annotations). Κάτω από το κρυπτογραφικό φίλτρο (crypt filter) AES-256, το καθένα υπόκειται σε επεξεργασία (processed) αυτόνομα (on its own): ένα φρέσκο τυχαίο (random) IV των 16-byte, CBC πάνω στα bytes, γέμισμα (block padding) σε όριο 16-byte, και το IV γραμμένο σε καθαρή μορφή (in the clear) μπροστά (ahead) από το κρυπτογραφημένο κείμενο (ciphertext)

Δύο συνέπειες ακολουθούν. Πρώτον, το κρυπτογραφημένο κείμενο είναι πάντα μεγαλύτερο (longer) από το απλό κείμενο: το IV προσθέτει 16 bytes και το γέμισμα (padding) προσθέτει 1 έως 16 επιπλέον, οπότε μια συμβολοσειρά (string) των 100-byte καταλαμβάνει 128 bytes στον δίσκο (disk) και μια άδεια ροή (stream) εξακολουθεί να παράγει 32. Κώδικας που προσαρμόζει το μέγεθος του buffer εξόδου στο μήκος της εισόδου, ή γράφει πίσω (writes back) μόνο τόσα bytes όσα διάβασε, παράγει αρχεία που αποτυγχάνουν να αποκρυπτογραφηθούν (fail to decrypt) στο τελευταίο μπλοκ κάθε αντικειμένου. Δεύτερον, το κόστος ακολουθεί τον αριθμό αντικειμένων (object count), όχι μόνο τον αριθμό των byte. Ένα σαρωμένο (scanned) αρχείο συγκεντρώνει τα bytes του σε μερικές μεγάλες ροές εικόνων, αλλά φέρει εκατοντάδες χιλιάδες κοντές (short) ροές και μικρές συμβολοσειρές (strings) όπου το επιπλέον κόστος ανά πράξη (per-operation overhead), και όχι το ίδιο το AES, είναι αυτό που προσθέτει τον λογαριασμό (is the bill)

Το μόνο έλεος στον σχεδιασμό του AES-256 είναι ο χειρισμός των κλειδιών (key handling). Οι διαχειριστές ασφαλείας (Security handlers) μέχρι την αναθεώρηση 4 παρήγαγαν (derived) ένα ξεχωριστό κλειδί για κάθε αντικείμενο συνδυάζοντας (hashing) το κλειδί του αρχείου με τον αριθμό του αντικειμένου και της γενιάς (generation numbers), επιβάλλοντας έναν νέο προγραμματισμό κλειδιών (key schedule) κάθε φορά. Τα σχήματα (schemes) της έκδοσης /V 5 εγκατέλειψαν την παραγωγή κλειδιού ανά αντικείμενο (per-object derivation): ένα τυχαίο κλειδί αρχείου 256-bit κρυπτογραφεί κάθε αντικείμενο μέσα στο έγγραφο. Το γεγονός αυτό επιτρέπει (licenses) κάθε βελτιστοποίηση (optimization) που ακολουθεί — η ακριβή κρυπτογραφική κατάσταση (state) μπορεί να χτιστεί μία φορά ανά αρχείο, όχι μία φορά ανά αντικείμενο

Το λεξικό (dictionary) /Encrypt της έκδοσης R6: ένα αργό άνοιγμα, φθηνά αντικείμενα

Ένα έγγραφο της αναθεώρησης (revision) 6 δηλώνει το σχήμα (scheme) του στο λεξικό /Encrypt του trailer, και οι καταχωρίσεις (entries) που έχουν σημασία χωρούν σε λίγες γραμμές:

/Filter /Standard
/V 5  /R 6  /Length 256
/CF << /StdCF << /CFM /AESV3  /Length 32  /AuthEvent /DocOpen >> >>
/StmF /StdCF    /StrF /StdCF
/O ...48 bytes...   /U ...48 bytes...
/OE ...32 bytes...  /UE ...32 bytes...
/Perms ...16 bytes...  /P -3904  /EncryptMetadata true

Το /V 5 επιλέγει την αρχιτεκτονική κλειδιού 256-bit και το /R 6 τον ενισχυμένο χαιρετισμό (hardened handshake) του ISO 32000-2. Το /CF ορίζει το ονομασμένο κρυπτογραφικό φίλτρο — το /AESV3 σημαίνει AES-256 σε λειτουργία CBC με το IV να προηγείται (prepended) — και τα /StmF και /StrF αναθέτουν (assign) αυτό το φίλτρο σε ροές και συμβολοσειρές (strings) αντίστοιχα. Τα /O, /U, /OE, και /UE περιέχουν το υλικό (material) επαλήθευσης (verification) κωδικού πρόσβασης και περιτύλιξης κλειδιού (key-wrapping), και το /Perms φέρει ένα κρυπτογραφημένο με AES αντίγραφο των bits δικαιωμάτων, ώστε ένας κακόβουλος επεξεργαστής (hostile editor) να μην μπορεί να αλλάξει (flip) αθόρυβα το /P

Η δομή (structure) του κόστους κρύβεται (hides) στα /OE και /UE. Το ξετύλιγμα (Unwrapping) του κλειδιού του αρχείου από αυτά τρέχει τον Αλγόριθμο 2.B, μια επαναληπτική (iterated) συνάρτηση παραγωγής κλειδιού (key-derivation function) που ενώνει (chaining) γύρους (rounds) SHA-256, SHA-384, και SHA-512 — τουλάχιστον 64 από αυτούς, με έναν κανόνα τερματισμού (stopping rule) που εξαρτάται από τα δεδομένα (data-dependent) — κατασκευασμένο εσκεμμένα (deliberately) αργά, ώστε η μαντεψιά κωδικών πρόσβασης (password guessing) να παραμένει ακριβή (expensive). Αυτό το τίμημα πληρώνεται μία φορά όταν ο δημιουργός (writer) παράγει (produces) το αρχείο και μία φορά όταν ένας αναγνώστης (reader) το ανοίγει, από μονοψήφια (single-digit) χιλιοστά του δευτερολέπτου (milliseconds) το καθένα. Σε ένα αρχείο με μισό εκατομμύριο αντικείμενα (half-million-object), το KDF (Key Derivation Function) είναι απλώς "θόρυβος" (noise), και αν μια αποθήκευση είναι αργή, ο Αλγόριθμος 2.B δεν είναι ο ύποπτος· ο βρόχος ανά αντικείμενο (per-object loop) είναι

Επαναχρησιμοποίηση της λαβής κλειδιού (key handle), επαναχρησιμοποίηση του προσωρινού buffer (scratch buffer)

Η απλοϊκή (naive) υλοποίηση είναι μια τακτοποιημένη βοηθητική συνάρτηση (utility function): ένας βοηθός EncryptAes256Cbc (helper) που ανοίγει τον CNG πάροχο των Windows (provider), επιλέγει CBC, δημιουργεί (generates) το αντικείμενο-κλειδί (key object), κρυπτογραφεί ένα buffer, και γκρεμίζει (tears down) τα πάντα. Σωστό, ελέγξιμο (unit-testable), και καταστροφικό μέσα σε έναν βρόχο 500.000 επαναλήψεων. Η τεκμηρίωση της Microsoft (documentation) επισημαίνει την BCryptOpenAlgorithmProvider ως ακριβή (expensive) και συνιστά την προσωρινή αποθήκευση (caching) της λαβής (handle), και η BCryptGenerateSymmetricKey τρέχει ολόκληρο τον προγραμματισμό του κλειδιού AES (key schedule) και δεσμεύει (allocates) την κατάσταση (state) του παρόχου — καθαρή σπατάλη όταν το κλειδί δεν αλλάζει ποτέ σε ολόκληρο το έγγραφο

Το Delphi RTL (Run-Time Library) δεν παρέχει ενσωματωμένο bcrypt import unit, οπότε δηλώστε τα entry points απευθείας. Η παρακάτω κλάση χτίζει (builds) όλη την κρυπτογραφική κατάσταση (state) μία φορά και στη συνέχεια κρυπτογραφεί οποιονδήποτε αριθμό αντικειμένων χωρίς εκχώρηση (allocation) σε κατάσταση σταθερής λειτουργίας (steady-state):

uses
  Winapi.Windows, System.SysUtils, System.Classes;

const
  BCRYPT_AES_ALGORITHM  = 'AES';
  BCRYPT_CHAINING_MODE  = 'ChainingMode';
  BCRYPT_CHAIN_MODE_CBC = 'ChainingModeCBC';
  BCRYPT_OBJECT_LENGTH  = 'ObjectLength';
  BCRYPT_BLOCK_PADDING            = $00000001;
  BCRYPT_USE_SYSTEM_PREFERRED_RNG = $00000002;

type
  NTSTATUS = Integer;
  BCRYPT_HANDLE = Pointer;

function BCryptOpenAlgorithmProvider(out hAlg: BCRYPT_HANDLE; AlgId,
  Impl: PWideChar; Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptCloseAlgorithmProvider(hAlg: BCRYPT_HANDLE;
  Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptSetProperty(hObj: BCRYPT_HANDLE; Prop: PWideChar; Input: PByte;
  cbInput, Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptGetProperty(hObj: BCRYPT_HANDLE; Prop: PWideChar; Output: PByte;
  cbOutput: ULONG; out cbResult: ULONG; Flags: ULONG): NTSTATUS; stdcall;
  external 'bcrypt.dll';
function BCryptGenerateSymmetricKey(hAlg: BCRYPT_HANDLE;
  out hKey: BCRYPT_HANDLE; KeyObj: PByte; cbKeyObj: ULONG; Secret: PByte;
  cbSecret: ULONG; Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptDestroyKey(hKey: BCRYPT_HANDLE): NTSTATUS; stdcall;
  external 'bcrypt.dll';
function BCryptEncrypt(hKey: BCRYPT_HANDLE; Input: PByte; cbInput: ULONG;
  Padding: Pointer; IV: PByte; cbIV: ULONG; Output: PByte; cbOutput: ULONG;
  out cbResult: ULONG; Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptGenRandom(hAlg: BCRYPT_HANDLE; Buffer: PByte;
  cbBuffer, Flags: ULONG): NTSTATUS; stdcall; external 'bcrypt.dll';

procedure CngCheck(Status: NTSTATUS; const Api: string);
begin
  if Status <> 0 then
    raise Exception.CreateFmt('%s failed, NTSTATUS 0x%.8x',
      [Api, Cardinal(Status)]);
end;

type
  TPdfObjectEncryptor = class
  private
    FAlg: BCRYPT_HANDLE;
    FKey: BCRYPT_HANDLE;
    FKeyObject: TBytes;  // CNG key-object workspace, allocated once
    FScratch: TBytes;    // ciphertext scratch, grows and then stays
  public
    constructor Create(const FileKey: TBytes);
    destructor Destroy; override;
    procedure EncryptObject(const Plain: TBytes; Dest: TStream);
  end;

constructor TPdfObjectEncryptor.Create(const FileKey: TBytes);
var
  Mode: string;
  ObjLen, Got: ULONG;
begin
  inherited Create;
  if Length(FileKey) <> 32 then
    raise Exception.Create('AES-256 file key must be 32 bytes');
  CngCheck(BCryptOpenAlgorithmProvider(FAlg, BCRYPT_AES_ALGORITHM, nil, 0),
    'BCryptOpenAlgorithmProvider');
  Mode := BCRYPT_CHAIN_MODE_CBC;
  CngCheck(BCryptSetProperty(FAlg, BCRYPT_CHAINING_MODE,
    PByte(PWideChar(Mode)), (Length(Mode) + 1) * SizeOf(WideChar), 0),
    'BCryptSetProperty');
  CngCheck(BCryptGetProperty(FAlg, BCRYPT_OBJECT_LENGTH, PByte(@ObjLen),
    SizeOf(ObjLen), Got, 0), 'BCryptGetProperty');
  SetLength(FKeyObject, ObjLen);
  // The AES key schedule is built once here and reused for every object
  CngCheck(BCryptGenerateSymmetricKey(FAlg, FKey, PByte(FKeyObject), ObjLen,
    PByte(FileKey), 32, 0), 'BCryptGenerateSymmetricKey');
end;

destructor TPdfObjectEncryptor.Destroy;
begin
  if FKey <> nil then
    BCryptDestroyKey(FKey);
  if FAlg <> nil then
    BCryptCloseAlgorithmProvider(FAlg, 0);
  inherited;
end;

procedure TPdfObjectEncryptor.EncryptObject(const Plain: TBytes; Dest: TStream);
var
  IV, IVWork: array[0..15] of Byte;
  Need, Written: ULONG;
  Src: PByte;
begin
  // Fresh random IV per object; it travels in the clear ahead of the data
  CngCheck(BCryptGenRandom(nil, @IV[0], 16, BCRYPT_USE_SYSTEM_PREFERRED_RNG),
    'BCryptGenRandom');
  Src := PByte(Plain);  // nil for an empty input is valid: padding-only block

  // Size query: CBC padding always adds 1..16 bytes, so Need > Length(Plain)
  IVWork := IV;  // BCryptEncrypt advances the IV buffer while it chains
  CngCheck(BCryptEncrypt(FKey, Src, Length(Plain), nil, @IVWork[0], 16,
    nil, 0, Need, BCRYPT_BLOCK_PADDING), 'BCryptEncrypt(size)');

  if ULONG(Length(FScratch)) < Need then
    SetLength(FScratch, Need);  // grows a handful of times, then stays put

  IVWork := IV;
  CngCheck(BCryptEncrypt(FKey, Src, Length(Plain), nil, @IVWork[0], 16,
    PByte(FScratch), Need, Written, BCRYPT_BLOCK_PADDING), 'BCryptEncrypt');

  // AESV3 layout: the 16-byte IV, then the padded ciphertext
  Dest.WriteBuffer(IV[0], 16);
  Dest.WriteBuffer(FScratch[0], Written);
end;

Τρεις λεπτομέρειες είναι καθοριστικές (load-bearing). Το ερώτημα μεγέθους (size query) — η πρώτη κλήση της BCryptEncrypt, με ένα κενό (nil) output buffer — επιστρέφει το μήκος του κρυπτογραφημένου κειμένου με το γέμισμα (padded ciphertext length), το οποίο δεν είναι ποτέ ίσο με το μήκος της εισόδου· το γέμισμα είναι ντετερμινιστικό (deterministic), επομένως μπορείτε να υπολογίσετε μόνοι σας το ((Len div 16) + 1) * 16 και να μειώσετε στο μισό τον αριθμό των κλήσεων, αλλά το ερώτημα (query) είναι η τεκμηριωμένη (documented) πρακτική (contract). Δεύτερον, η BCryptEncrypt προχωρά (advances) το IV buffer στη θέση του (in place) όσο ενώνει (chains), άρα ένα λειτουργικό αντίγραφο (working copy) μπαίνει σε κάθε κλήση και το παρθένο (pristine) IV καταλήγει (lands) στην έξοδο. Τρίτον, το FScratch μόνο μεγαλώνει (grows), μέχρι να φτάσει το μέγεθος του μεγαλύτερου αντικειμένου στο αρχείο, μετά το οποίο ο βρόχος δεν εκχωρεί (allocates) τίποτα

Τι αξίζει η επαναχρησιμοποίηση της λαβής (handle reuse), μετρημένη

Το αρχείο που επέβαλε (forced) αυτή την άσκηση (exercise) ήταν ένα σαρωμένο (scanned) αρχείο δανείου (loan archive) 1.8 GB: 412.000 κρυπτογραφημένα αντικείμενα που έφεραν (carrying) 1.710 MB ωφέλιμου φορτίου (payload), αφού αφαιρεθεί η δομή (structure) του απλού κειμένου (plaintext). Το ίδιο μηχάνημα, το ίδιο αρχείο, αποθήκευση (storage) NVMe, ένα νήμα (thread):

  • Προετοιμασία ανά κλήση (Per-call setup) (άνοιγμα του παρόχου και παραγωγή κλειδιού μέσα στη βοηθητική συνάρτηση/helper): φάση κρυπτογράφησης 71.3 s — 1.710 MB ÷ 71.3 s ≈ 24 MB/s
  • Κατάσταση εξυψωμένη (State hoisted) (η παραπάνω κλάση): 9.6 s — 1.710 MB ÷ 9.6 s ≈ 178 MB/s

Η διαφορά είναι 61.7 s σε 412.000 κλήσεις, ή περίπου 150 µs ανά κλήση που δαπανήθηκαν για το άνοιγμα (opening) ενός παρόχου, τη ρύθμιση μιας λειτουργίας αλυσίδας (chaining mode), και την ανακατασκευή (rebuilding) ενός προγράμματος κλειδιού (key schedule) για ένα κλειδί που δεν άλλαξε ποτέ. Τίποτα από αυτά δεν ήταν κρυπτογραφία. Με το AES-NI, η κρυπτογράφηση CBC μεγάλων buffers τρέχει (runs) κοντά στα 1.4 GB/s σε έναν πυρήνα (core), άρα η ίδια η αριθμητική (arithmetic) του AES ευθύνεται για περίπου 1.2 s από τα 9.6· τα περισσότερα από τα υπόλοιπα (rest) είναι οι δύο μεταβάσεις (transitions) BCryptEncrypt σε user-mode ανά αντικείμενο συν η δημιουργία του IV ανά αντικείμενο. Η ομαδοποίηση (Batching) των IVs — μία κλήση BCryptGenRandom που γεμίζει 4.096 από αυτά — μείωσε τον χρόνο εκτέλεσης (trimmed the run) στα 8.9 s. Πέρα από αυτό βρίσκεστε στο κατώτατο όριο (floor) ανά αντικείμενο του API, και ο μόνος εναπομείνας μοχλός (lever) είναι ο παραλληλισμός (parallelism): τα αντικείμενα της έκδοσης /V 5 είναι ανεξάρτητα (independent) κάτω από το κοινόχρηστο (shared) κλειδί του αρχείου, επομένως τέσσερα νήματα εργατών (worker threads) με ένα αντικείμενο-κλειδί (key object) το καθένα έριξαν τη φάση (took the phase) στα 3.1 s πριν ο writer εξόδου (output writer) γίνει το σημείο σειριοποίησης (serialization point)

Πλήρης επανεγγραφή (Full rewrite) έναντι αυξητικής αποθήκευσης (incremental save)

Η κοκκώδης υφή (Granularity) αποφασίζει επίσης τι κοστίζει μια αποθήκευση (save). Η προσθήκη κρυπτογράφησης σε ένα υπάρχον (existing) έγγραφο απλού κειμένου (plaintext) ξαναγράφει (rewrites) κάθε αντικείμενο εξ ορισμού (by definition): κάθε ροή (stream) και κάθε συμβολοσειρά (string) αλλάζει τόσο σε περιεχόμενο (content) όσο και σε μήκος (length), κάθε offset διασταυρούμενης αναφοράς μετακινείται, και δεν υπάρχει διαθέσιμη αυξητική (incremental) διαδρομή. Προϋπολογίστε (Budget) το σαν μια πλήρη (full) σειριακή (sequential) επανεγγραφή (rewrite), και γράψτε σε ένα προσωρινό (temporary) αρχείο το οποίο θα μετονομαστεί (renamed) πάνω από το αρχείο-στόχο (target), διότι διαφορετικά, μια κατάρρευση (crash) στη μέση της κρυπτογράφησης αφήνει (leaves) ένα μισο-κρυπτογραφημένο (half-ciphered) αρχείο που κανένας κωδικός πρόσβασης δεν θα ανοίξει

Η αντίστροφη (reverse) κατεύθυνση είναι η φθηνή (cheap). Μόλις (Once) ένα αρχείο κρυπτογραφηθεί (encrypted), μια αυξητική (incremental) ενημέρωση προσαρτά (appends) νέα αντικείμενα (objects) κρυπτογραφημένα (encrypted) με το ίδιο (same) κλειδί αρχείου (file key) και αφήνει (leaves) κάθε (every) αρχικό byte (original byte) άθικτο (untouched). Το να σφραγίσετε (Stamping) μια σημείωση (annotation) έγκρισης (approval) σε ένα (onto a) κρυπτογραφημένο (encrypted) αρχείο (archive) των 2 GB κοστίζει (costs) kilobytes προσαρτημένης (appended) εξόδου (output), όχι μια επανεγγραφή (rewrite) 2 GB. Το συμπέρασμα για τις γραμμές (pipeline corollary) επεξεργασίας: κρυπτογραφήστε μία φορά, ως το τελευταίο βήμα (step) της εργασίας (job), και αφήστε (let) τις (the) επόμενες (subsequent) πινελιές (touches) να στηριχτούν (ride) σε αυξητικές (incremental) αποθηκεύσεις (saves). Μια εναλλαγή (rotation) του (the) κωδικού πρόσβασης (password) που (which) επίσης (also) εναλλάσσει (rotates) το κλειδί του αρχείου είναι και πάλι μια πλήρης (full) επανεγγραφή (rewrite) — προγραμματίστε (schedule) την σαν τέτοια (like one)

Μέτρηση της απόδοσης (throughput) χωρίς να ξεγελάτε τον εαυτό σας

Οι ισχυρισμοί (claims) απόδοσης (throughput) της κρυπτογράφησης τείνουν να είναι λάθος (wrong) στον αριθμητή (numerator), στον παρονομαστή (denominator), ή και στα δύο. Ο αριθμητής θα έπρεπε να είναι τα (the) ωφέλιμα bytes (payload bytes): το άθροισμα των (the sum of) μηκών (lengths) των (the) ροών και συμβολοσειρών (streams and strings) που (that) πραγματικά (actually) πέρασαν (pushed) μέσα από το AES, μετά τη συμπίεση (compression), τα οποία ο (which the) writer μπορεί να αθροίσει στην πορεία (as it goes). Το (The) μέγεθος του αρχείου την (overstates) υπερεκτιμά (it) — το παραπάνω αρχείο (archive) είναι 1.8 GB στον (on) δίσκο (disk), αλλά μόνο 1.710 MB από (of) αυτό (it) αγγίζουν (ever touches) τον (the) κρυπτογράφο (cipher). Ο (The) παρονομαστής θα έπρεπε (should) να είναι (be) η (the) φάση κρυπτογράφησης (encryption phase) μόνο (alone), περιβαλλόμενη (bracketed) με ένα TStopwatch (με) από (from) το System.Diagnostics, με την ανάλυση (parsing), το (the) deflate, και τις (the) εισόδους/εξόδους του δίσκου (disk I/O) (να βρίσκονται) έξω (outside) από (the) τις αγκύλες (brackets). (Αν) Ενσωματώστε (Fold) αυτά (those) μέσα (in) και (and) ο ίδιος (the identical) κώδικας (encryption code) κρυπτογράφησης (will) θα (measure) μετρήσει (several times) αρκετές φορές (slower) πιο αργός (on a file) σε ένα αρχείο που (that) απλώς συμπιέζεται χειρότερα (merely compresses worse). Τα παραπάνω στοιχεία (The figures above) είναι συγκρίσιμα (comparable) ακριβώς επειδή (precisely because) και οι (both) δύο πλευρές (sides) της (the) διαίρεσης αφορούν μόνο την κρυπτογράφηση (are encryption-only)

Τίποτα (None) από αυτά δεν (of this has to) χρειάζεται (be) να είναι (code) κώδικας που σας ανήκει (you own). Το (The) HotPDF τυλίγει (wraps) την (the) ίδια (same) μηχανική (engineering) πίσω (behind) από ιδιότητες των στοιχείων (component properties) — ActivateProtection, CryptKeyLength, UseAES256R6 — στο σωστό ύψος (at the right altitude) για διαδραστικές (interactive) εφαρμογές (VCL applications) VCL, με (with the) τις παγίδες (pitfalls) της σειράς (order) εκχώρησης (assignment) να (covered) καλύπτονται στο (in the) άρθρο για το (the) HotPDF AES-256. Για μη-εποπτευόμενες (For unattended) ροές εργασίας (pipelines), η (the) PDFlibPas εφαρμόζει (applies) το AES-256 (αναθεώρηση 6 / revision 6) (σε) στα (to) υπάρχοντα (existing) αρχεία (files) σε μία μόλις κλήση (in a single) EncryptFile (call) στο Strength 4 και (and) επαληθεύει (verifies) εκ των υστέρων (afterward) το τι (what) προσγειώθηκε (landed) στον (on) δίσκο (disk), μια ροή εργασίας (a workflow) που αναλύεται βήμα-βήμα στο (walked through in the) άρθρο (για τον) PDFlibPas encryption audit (έλεγχο κρυπτογράφησης)

Οι (The) διαδρομές (encryption paths) κρυπτογράφησης που (described) περιγράφονται εδώ (here) αποστέλλονται (ship) με το (in the) HotPDF Component (για) για Delphi και C++Builder και (and) (με) (τη) στην βιβλιοθήκη PDFlibPas (library)· (και) και οι δύο προϊοντικές σελίδες (product pages) φέρουν (carry) την (the) πλήρη (complete) αναφορά κρυπτογράφησης (encryption reference)