ในภาคกฎหมาย การเงิน และการดูแลสุขภาพ การสร้างเอกสาร PDF จำนวนมากถือเป็นวิธีปฏิบัติมาตรฐาน อย่างไรก็ตาม การผลิตเอกสารเป็นเพียงครึ่งหนึ่งของงานเท่านั้น การรักษาความปลอดภัยก็มีความสำคัญไม่แพ้กัน เมื่อคุณมีไปป์ไลน์เก็บถาวรที่ประมวลผล PDF หลายร้อยกิกะไบต์ทุกวัน การนำการเข้ารหัส AES-256 ไปใช้สามารถกลายเป็นคอขวดของประสิทธิภาพได้อย่างรวดเร็ว
ในบทความนี้ เราจะมาสำรวจวิธีการเข้ารหัส PDF ด้วย AES-256 ความเร็วสูงใน Delphi โดยหลีกเลี่ยงหน่วยความจำหมดและปรับรอบการเข้ารหัสให้เหมาะสม
ข้อกำหนดการเข้ารหัส PDF
ความปลอดภัยของ PDF มีการพัฒนาอย่างมาก เวอร์ชันแรกๆ ใช้ RC4 แบบ 40 บิต ซึ่งปัจจุบันสามารถเจาะได้อย่างง่ายดาย มาตรฐานปัจจุบัน (PDF 1.7 Extension Level 3 และ PDF 2.0) กำหนดให้ใช้การเข้ารหัส AES-256
ในไฟล์ PDF คุณไม่ได้เข้ารหัสบล็อกไฟล์ทั้งหมด โครงสร้างเอกสาร (ตาราง XRef และโครงสร้างของพจนานุกรม) จะยังคงเป็นข้อความธรรมดา แต่คุณจะเข้ารหัส Streams (ข้อมูลดิบสำหรับรูปภาพและเนื้อหาหน้า) และ Strings (เช่น ข้อความเมตาดาต้า) สิ่งนี้ต้องการให้ตัวแยกวิเคราะห์ดึงข้อมูล นำไปผ่าน AES CBC (Cipher Block Chaining) และเขียนกลับคืน
คอขวด: การโหลดเข้าสู่หน่วยความจำ
ข้อผิดพลาดทั่วไปเมื่อเข้ารหัส PDF ขนาดใหญ่ (เช่น เอกสารสแกน 2GB) คือการโหลดสตรีมทั้งหมดลงใน TMemoryStream ก่อนส่งผ่านไปยังเอนจินการเข้ารหัส สิ่งนี้นำไปสู่ข้อผิดพลาดหน่วยความจำไม่เพียงพอ (Out-Of-Memory: OOM) ในกระบวนการแบบ 32 บิต และเกิดปัญหาการสลับหน้า (page faulting) อย่างมหาศาลในกระบวนการแบบ 64 บิต
การเข้ารหัสแบบสตรีมมิ่งใน Delphi
ทางออกคือการใช้วิธีการแบบสตรีมมิ่งที่มีการแบ่งส่วน (chunked) ด้วยการใช้ Windows Cryptography API: Next Generation (CNG) หรือไลบรารีอย่าง OpenSSL คุณสามารถอ่านสตรีม PDF ในบล็อก 64KB เข้ารหัสบล็อกนั้น และเขียนลงในสตรีมดิสก์เอาต์พุตโดยตรง
นี่คือตัวอย่างเชิงแนวคิดใน Delphi ที่แสดงลูปการเข้ารหัสแบบมีบัฟเฟอร์สำหรับสตรีม:
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;
การปรับส่วนหลังการเข้ารหัสให้เหมาะสมที่สุด
นักพัฒนา Delphi มีทางเลือกหลายทางสำหรับส่วนหลัง (backend) ของ AES:
- การใช้ Delphi แบบเนทีฟ: ใช้งานง่าย แต่มักจะทำงานช้ากว่าเนื่องจากทำงานบนซอฟต์แวร์ล้วนๆ
- Windows CNG (BCrypt): ได้รับการปรับแต่งให้เหมาะสมที่สุดและสามารถใช้การเร่งความเร็วด้วยฮาร์ดแวร์ (คำสั่ง AES-NI บนซีพียู Intel/AMD สมัยใหม่)
- OpenSSL (libcrypto): มาตรฐานอุตสาหกรรม มีความเร็วสูงอย่างไม่น่าเชื่อ แต่ต้องพกพา DLL ภายนอกไปด้วย
สำหรับแอปพลิเคชันเซิร์ฟเวอร์ที่มีทรูพุตสูง (high-throughput) จำเป็นต้องใช้การเร่งความเร็วด้วยฮาร์ดแวร์ AES-NI เมื่อใช้ Windows CNG ใน Delphi การจับคู่ฟังก์ชัน BCryptEncrypt ช่วยให้แอปพลิเคชันของคุณโอนงานหนักไปยังซิลิกอนการเข้ารหัสเฉพาะของ CPU ได้ ซึ่งจะช่วยลดโอเวอร์เฮดของการเข้ารหัสให้เข้าใกล้ศูนย์ได้อย่างมีประสิทธิภาพ
บทสรุป
เมื่อทำการเข้ารหัส PDF ขนาดกิกะไบต์ ให้พึ่งพาการแบ่งสตรีม (stream chunking) แทนการใช้บัฟเฟอร์หน่วยความจำทั้งหมด และตรวจสอบให้แน่ใจว่าส่วนหลังการเข้ารหัสของคุณใช้การเร่งความเร็วด้วยฮาร์ดแวร์ AES-NI การผสมผสานนี้จะรับประกันได้ว่าไปป์ไลน์การจัดเก็บข้อมูลของคุณทำงานด้วยความเร็วของไดรฟ์ NVMe ของคุณแทนที่จะถูกจำกัดด้วย CPU
หมายเหตุ: การเข้ารหัส AES-256 ความเร็วสูงโดยใช้การสตรีมแบบแบ่งส่วนได้รับการรองรับแบบเนทีฟในคอมโพเนนต์ HotPDF VCL Component