Technical Article

הצפנת AES-256 במהירות גבוהה עבור מסמכי PDF עצומים

במגזרים המשפטיים, הפיננסיים והרפואיים, יצירת כמויות גדולות של מסמכי PDF היא פרקטיקה סטנדרטית. עם זאת, הפקת המסמכים היא רק חצי מהעבודה; אבטחתם קריטית לא פחות. כאשר יש לכם צינור נתונים ארכיוני המעבד מאות גיגה-בתים של קבצי PDF מדי יום, החלת הצפנת AES-256 עלולה להפוך במהירות לצוואר בקבוק בביצועים.

במאמר זה, נחקור כיצד להשיג הצפנת AES-256 מהירה לקבצי PDF ב-Delphi על ידי הימנעות ממיצוי זיכרון ומיטוב לולאות הקריפטוגרפיה.

מפרט ההצפנה של PDF

האבטחה של PDF התפתחה משמעותית. גרסאות מוקדמות השתמשו ב-RC4 של 40 סיביות, שניתן לפרוץ בקלות כיום. התקן הנוכחי (PDF 1.7 Extension Level 3 ו-PDF 2.0) מחייב הצפנת AES-256.

בקובץ PDF, לא מצפינים את קובץ הבלוק כולו. מבנה המסמך (טבלת ה-XRef ומבנה המילונים) נשאר טקסט קריא (plaintext). במקום זאת, מצפינים את ה-Streams (הנתונים הגולמיים של תמונות ותוכן הדף) וה-Strings (כגון טקסט של מטא-נתונים). זה דורש מהמנתח (parser) לחלץ את הנתונים, להחיל הצפנת AES CBC (Cipher Block Chaining), ולכתוב אותם בחזרה.

צוואר הבקבוק: טעינה לזיכרון

טעות נפוצה בעת הצפנת PDF עצום (למשל, ארכיון סרוק בנפח 2GB) היא טעינת הזרם כולו לתוך TMemoryStream לפני העברתו למנוע ההצפנה. הדבר מוביל לחריגות Out-Of-Memory (OOM) בתהליכי 32 סיביות, ולתקלות דפים (page faulting) מסיביות בתהליכי 64 סיביות.

הזרמת הצפנה ב-Delphi

הפתרון הוא להשתמש בגישת הזרמה (streaming) המבוססת על מקטעים. באמצעות Windows Cryptography API: Next Generation (CNG) או ספרייה כמו OpenSSL, תוכלו לקרוא את זרם ה-PDF בבלוקים של 64KB, להצפין את הבלוק ולכתוב אותו ישירות לזרם הדיסק של הפלט.

להלן דוגמה רעיונית ב-Delphi המדגימה לולאת הצפנה מחוצצת (buffered encryption loop) עבור זרם:

uses
  System.Classes, System.SysUtils;

const
  BUFFER_SIZE = 65536; // 64KB chunks

// This represents your AES-256 encryption routine
procedure EncryptStreamChunk(const InBuffer; var OutBuffer; BytesRead: Integer; const Key: TBytes; const IV: TBytes);
begin
  // Call to Windows CNG (BCryptEncrypt) or OpenSSL (EVP_EncryptUpdate)
  // ...
end;

procedure EncryptLargePDFStream(InputStream, OutputStream: TStream; const Key, IV: TBytes);
var
  InBuffer, OutBuffer: array of Byte;
  BytesRead: Integer;
begin
  SetLength(InBuffer, BUFFER_SIZE);
  // AES CBC requires padding, so the output buffer must be slightly larger
  SetLength(OutBuffer, BUFFER_SIZE + 16); 

  InputStream.Position := 0;
  OutputStream.Position := 0;

  repeat
    BytesRead := InputStream.Read(InBuffer[0], BUFFER_SIZE);
    if BytesRead > 0 then
    begin
      EncryptStreamChunk(InBuffer[0], OutBuffer[0], BytesRead, Key, IV);
      // Write the encrypted cipher text directly to disk
      OutputStream.Write(OutBuffer[0], BytesRead); // Note: padding logic omitted for brevity
    end;
  until BytesRead < BUFFER_SIZE;
end;

מיטוב מנוע ההצפנה האחורי (Cryptographic Backend)

למפתחי Delphi יש מספר אפשרויות עבור מנוע ה-AES האחורי:

  • יישומי Delphi מקוריים: קלים לפריסה, אך לרוב איטיים יותר מכיוון שהם מבוצעים אך ורק בתוכנה.
  • Windows CNG (BCrypt): ממוטב מאוד ויכול לנצל האצת חומרה (פקודות AES-NI במעבדי Intel/AMD מודרניים).
  • OpenSSL (libcrypto): תקן התעשייה, מהיר להפליא, אך דורש הפצת קבצי DLL חיצוניים.

עבור יישומי שרת עם תפוקה גבוהה, האצת חומרה באמצעות AES-NI היא בגדר חובה. בעת שימוש ב-Windows CNG ב-Delphi, מיפוי הפונקציה BCryptEncrypt מאפשר ליישום שלכם להעביר את העבודה הקשה לסיליקון ההצפנה הייעודי של המעבד (CPU), ובכך להפחית למעשה את תקורת ההצפנה לכמעט אפס.

סיכום

בעת הצפנת קבצי PDF בנפח של גיגה-בתים, הסתמכו על מקטעי זרמים (stream chunking) במקום על חציצת זיכרון מלאה, וודאו שמנוע ההצפנה שלכם מנצל האצת חומרה של AES-NI. שילוב זה מבטיח שצינור הארכיון שלכם יפעל במהירות של כונני ה-NVMe שלכם, במקום להיות מוגבל על ידי המעבד (CPU-bound).

הערה: הצפנת AES-256 במהירות גבוהה תוך שימוש בהזרמת מקטעים (chunked streaming) נתמכת באופן מובנה ב-HotPDF VCL Component.