PDF Library for Delphi เปิด PDF ที่เข้ารหัสจาก raw file encryption key ได้โดยไม่ต้องใช้ password DAOpenFileWithEncryptionKey รับ key เป็นข้อความ hexadecimal ตรวจสอบกับ verifier ที่เก็บอยู่ใน encryption dictionary แล้วคืน read-only Direct Access handle ส่วน DAOpenFromStreamWithEncryptionKey ทำแบบเดียวกันกับ TStream ที่ caller เป็นเจ้าของ ทั้งสอง entry point มีมาตั้งแต่ v3.496.0
สถานการณ์นี้เฉพาะทางแต่เกิดขึ้นจริง งาน forensics อาจส่ง key ที่กู้ได้จาก memory image มาให้โดยไม่มี password งาน archival จำนวนมากอาจมีเอกสารหมื่นไฟล์ที่ file key อยู่ใน escrow database เพราะ DRM system ต้นทางเลิกออก password มาหลายปีแล้ว หรือการย้ายออกจาก rights-management product ที่เลิกใช้อาจมี key material เหลืออยู่แต่ไม่มีอย่างอื่น ในทุกกรณี credential ที่คุณถือคือผลลัพธ์ของ key derivation ไม่ใช่ input และไม่มี password parameter ใดใน API ที่รับมันได้
ทำไม file encryption key จึงไม่ใช่ password
Password กับ file encryption key อยู่คนละฝั่งของ key derivation ใน PDF standard security handler (ISO 32000-1 §7.6.3) handler จะนำ password ไปผสมกับ /O, /P, file ID และ revision-specific hash เพื่อสร้าง file key หากนำ file key ไปใส่ในช่อง password คุณจะได้ค่าหนึ่งที่ถูก hash ต่อเป็นอีกค่าหนึ่งที่ไม่มีความหมาย นี่จึงต้องมี entry point ของตัวเอง ไม่ใช่แค่ flag บน DAOpenFile
จุดที่ inject raw key ได้ถูกกำหนดตาม revision ใน revision 2 ถึง 4 ยังมีการสร้าง per-object key แยกจาก file key, object number, generation number และสำหรับ AESV2 ก็รวม AES salt ด้วย ดังนั้นการถือ file key ไม่ได้ทำให้ข้าม object-level derivation ได้เลย ส่วน revision 5 ถึง 7 ใช้ file key ขนาด 32 byte โดยตรงกับ AES-256 และไม่มีขั้นตอนต่อ object สิ่งที่ทั้งสองกลุ่มมีร่วมกันคือ file key เอง ดังนั้นนี่จึงเป็นจุดเดียวที่ PDF Library for Delphi รับ key จากภายนอก หากคุณยังมี password ให้ใช้เส้นทางปกติและปล่อยให้ password retry lifecycle สำหรับความพยายามครั้งแรกที่ผิด จัดการ เพราะ raw-key entry ตั้งใจตัดความสะดวกหลายอย่างที่ password path ยังมีอยู่
DAOpenFileWithEncryptionKey รับ input แบบใด
รับเฉพาะ ASCII hex ที่ไม่มี prefix ไม่มี whitespace และมีจำนวนอักขระเป็นเลขคู่ โดยจำนวน byte ที่ decode แล้วต้องตรงกับ revision ของ encryption ทุกประการ สำหรับ revision 2 ถึง 4 ความยาวที่คาดหวังมาจาก /Length ใน encryption dictionary ซึ่งเป็นจำนวน bit ที่หารด้วย 8 ลงตัวและอยู่ระหว่าง 5 ถึง 16 byte หากไม่มี /Length จะใช้ค่าเริ่มต้น 40 bit ส่วน revision 5 ถึง 7 ต้องเป็น 32 byte เท่านั้น ไม่มีการเจรจาค่า prefix 0x, จำนวน digit คี่, hex เกิน 64 ตัว, bit ที่ไม่รู้จักใน Options หรือ document ที่ไม่ได้เข้ารหัส ล้วนให้ผลเหมือนกัน คือ handle เป็น 0 และ LastErrorCode เป็น PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID ซึ่งมีค่า 425 ความเข้มงวดนี้เป็นสิ่งที่ต้องการ parser ที่ผ่อนปรนซึ่ง trim whitespace และเติม zero ให้ input สั้นจะเปลี่ยน clipboard paste ที่ถูกตัดให้กลายเป็น credential แล้วไปล้มเหลวในจุดที่อ่านอาการได้ยากกว่ามาก
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex คือ 32 hex characters สำหรับ AES-128 R4 และ 64 สำหรับ AES-256 R6
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If FileHandle= 0 Then
Raise Exception.CreateFmt('raw key refused (error %d)', [Lib.LastErrorCode]);
Try
PageRef:= Lib.DAFindPage(FileHandle, 1);
WriteLn(Lib.DAExtractPageText(FileHandle, PageRef, 0));
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Lib.Free;
End;
End;
error code อื่นยังแยกจากกันเพื่อให้ batch job แยก operator error ออกจากปัญหาของ evidence ได้: 411 เมื่อไฟล์ไม่มีอยู่ 401 เมื่อเปิดอ่านไม่ได้ และ 409 เมื่อ cross-reference structure เสีย ส่วนทุกอย่างที่เกี่ยวกับ key รวมเป็น 425 โดยตั้งใจ เพราะ raw-key entry point ที่บอกว่า key ผิดตรงส่วนไหนคือ oracle
การตรวจสอบพิสูจน์อะไรจริง
PDF Library for Delphi พิสูจน์ว่า key ที่ส่งมาเป็นของ document นี้ โดยใช้ verifier ที่ encryption dictionary มีอยู่แล้ว และวิธีตรวจต่างกันตาม revision Revision 2 จะคำนวณ RC4 encryption ของ standard padding string ขนาด 32 byte ใหม่ แล้วเทียบทั้ง 32 byte กับ /U Revision 3 และ 4 จะ hash padding ร่วมกับ file ID รัน RC4 pass พร้อม 19 รอบที่ได้จาก XOR แล้วเทียบ 16 byte แรกของ /U ส่วน revision 5 ถึง 7 จะ decrypt string /Perms ขนาด 16 byte ด้วย zero IV แล้วตรวจสี่อย่างอย่างอิสระพร้อมกัน ได้แก่ permission word แบบ little-endian กับ /P, byte FF สี่ตัวที่ตำแหน่ง 5 ถึง 8, encrypt-metadata flag ที่เป็น T หรือ F และ marker adb ที่ตำแหน่ง 10 ถึง 12
เมื่อมี verifier แต่ตรวจไม่ตรง การเปิดจะถูกปฏิเสธโดยไม่มีเงื่อนไข นี่คือ guarantee ที่ฟีเจอร์ทั้งหมดตั้งอยู่บนมัน ต้องเข้าใจด้วยว่าการตรวจสอบไม่ใช่อะไร มันบอกว่า key decrypt ไฟล์นี้ได้ ไม่ได้บอกว่าใครอนุญาตให้คุณใช้ไฟล์ permission word ที่กู้จาก /Perms เป็นหลักฐานเกี่ยวกับ key ไม่ใช่ใบอนุญาต และหากต้องการรู้ว่า document ระบุว่าอนุญาตอะไร นั่นเป็นงานแยกสำหรับ encryption and permissions audit pass ปัญหาการ normalize ฝั่ง password เช่น SASLprep สำหรับ password AES-256 ที่ไม่ใช่ ASCII ไม่เกิดที่นี่ เพราะไม่มี string ใดถูกส่งเข้า hash
PDF_RAW_KEY_ALLOW_UNVERIFIED ใช้เมื่อใด
PDF_RAW_KEY_ALLOW_UNVERIFIED ครอบคลุมสถานการณ์เดียว คือ document ไม่มี verifier ที่ใช้งานได้ เพราะ /Perms หายไปหรือไม่ยาว 16 byte หรือ /U สั้นเกินกว่าจะเปรียบเทียบได้ มันไม่สามารถ override หลักฐานที่ตรวจแล้วไม่ผ่านได้ หากแก้ hex digit หนึ่งตัวใน /Perms แล้วส่ง key ที่ถูกต้องพร้อมเปิด option นี้ PDF Library for Delphi ก็ยังคืน 0 และ 425 หากส่ง key ที่เป็น zero byte 32 ตัวเทียบกับ verifier ที่สมบูรณ์พร้อมเปิด option คำตอบก็เหมือนเดิม option นี้ผ่อนคลายการไม่มีหลักฐาน ไม่ใช่ความขัดแย้งกับหลักฐาน เพราะ recovery open กับ verified open เป็นสถานะทางความรู้คนละแบบ จึงรายงานแยกกันแทนการรวมเข้าไปใน return value และ DAGetEncryptionKeyValidation รับ open handle แล้วตอบหนึ่งใน constant สามค่า
PDF_RAW_KEY_VALIDATION_VERIFIED(1) หมายถึงมี verifier และตรวจตรงกันPDF_RAW_KEY_VALIDATION_UNVERIFIED(2) หมายถึงยอมรับ key เพราะไม่มี verifier ให้ประเมิน และ caller ขอ policy นี้อย่างชัดเจนPDF_RAW_KEY_VALIDATION_NONE(0) คือค่าที่ handle ซึ่งเปิดด้วย password ปกติรายงาน
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// ไม่มี verifier ในไฟล์นี้หรือ ใช้ recovery policy ที่ระบุอย่างชัดเจนลองใหม่
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex,
PDF_RAW_KEY_ALLOW_UNVERIFIED);
If FileHandle<> 0 Then
Begin
Case Lib.DAGetEncryptionKeyValidation(FileHandle) Of
PDF_RAW_KEY_VALIDATION_VERIFIED:
Chain.Note('key verified against the encryption dictionary');
PDF_RAW_KEY_VALIDATION_UNVERIFIED:
Chain.Note('no verifier available: extraction is unattested');
End;
End;
อ่านอย่างเดียวโดยโครงสร้าง และใครเป็นเจ้าของ stream
file entry แบบ raw-key จะเปิด source ด้วย fmOpenRead or fmShareDenyWrite เสมอ และทำให้ Direct Access chain ทั้งหมดเป็น read-only ดังนั้น DAAppendFile จะปฏิเสธการเขียน in-place และคืนค่า 2 แทนการพยายามทำ incremental update นี่ไม่ใช่ policy ที่คุณขอให้ handle เปลี่ยนใจได้ เพราะมันถูกกำหนดใน constructor ก่อนเริ่ม parse ไฟล์ด้วยซ้ำ สำหรับงาน evidence คุณสมบัติที่ต้องการคือ source byte ต้องเหมือนเดิมทุก byte หลังปิด handle และ regression suite ยืนยันเรื่องนี้ทั้งกับ fixture AES-128 revision 4 และ AES-256 revision 6 ส่วน stream entry มีพฤติกรรมด้าน write เหมือนกันและเพิ่มกฎอีกข้อคือ DAOpenFromStreamWithEncryptionKey ไม่รับ ownership ดังนั้น DACloseFile จะไม่ทำลาย TStream ของคุณ และคุณต้อง free เอง
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = read-only handle: export ไปที่อื่นได้ และห้าม append ลงใน evidence
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // handle ไม่เคยเป็นเจ้าของ stream นี้
End;
สุขอนามัยของ key และสิ่งที่ DLL export
การปิด chain จะเขียนทับ file key, password cache และ derived object key และ decoded key จะถูกล้างใน Finally block ของ entry point เองไม่ว่า open จะสำเร็จหรือไม่ มีรายละเอียดหนึ่งที่ทำให้ต้อง debug จริง: copy ใดก็ตามที่ต้องอยู่รอดจะถูก clone อย่าง explicit ด้วย SetLength และ Move แทนการ assign หาก assign AnsiString ตัวหนึ่งให้อีกตัวใน Delphi ชื่อทั้งสองจะ share buffer เดียวกันภายใต้ copy-on-write ดังนั้นการล้างฝั่ง caller จะล้าง key ที่ crypt handler กำลังใช้อยู่ด้วย และ document จะ decrypt ออกมาเป็น garbage โดยไม่มี stack trace อธิบายได้ entry point ที่เป็น file เท่านั้นที่ข้าม DLL boundary ได้ ทั้งรูปแบบ wide และ ANSI พร้อม accessor สำหรับ validation status ส่วน variant ที่รับ TStream ยังเป็น Delphi-only เพราะพึ่งพา object lifetime และ reference semantics ของ Delphi ซึ่งไม่มีตัวแทนที่ตรงไปตรงมาใน flat C ABI หาก recovery tooling ของคุณเป็น DLL client ให้เตรียม staging ลง temporary file และลบไฟล์ภายใต้การควบคุมแบบเดียวกับที่ใช้กับ key
ปฏิบัติต่อ hex key ในฐานะ credential material ด้วยกฎการจัดการแบบเดียวกับ document password และเก็บ validation status ไว้ใน log ที่ chain of custody ของคุณสร้าง เพื่อให้ผู้อ่านภายหลังแยก verified extraction ออกจาก extraction ที่ไม่มีหลักฐานรับรองได้ หากกำลังประเมิน Delphi PDF component สำหรับงาน forensics, bulk archival หรือ DRM migration raw-key entry point, read-only guarantee และ encryption audit surface ล้วนอยู่ในไลบรารีเดียวกัน และอ่านรายการฟีเจอร์ทั้งหมดได้ที่ หน้าผลิตภัณฑ์ PDF Library for Delphi