flag การอนุญาตของ PDF ไม่ใช่กุญแจล็อก มันเป็นเพียงคำขอที่ไฟล์ยื่นต่อโปรแกรมที่เปิดมันขึ้นมา และ viewer มีอิสระที่จะเพิกเฉยต่อคำขอนั้นได้ ข้อเท็จจริงข้อเดียวนี้เป็นตัวกำหนดว่าควรคิดเกี่ยวกับตัวเลือกอื่น ๆ ทั้งหมดในหน้านี้อย่างไร ความลับที่แท้จริงมาจากที่เดียวเท่านั้น: การเข้ารหัส AES-256 ที่ผูกกับรหัสผ่านที่ผู้อ่านไม่มี ส่วนที่เหลือทั้งหมด ไม่ว่าจะเป็นช่องกาเครื่องหมาย "ห้ามพิมพ์" และ "ห้ามคัดลอก" ล้วนเป็นนโยบายที่ซอฟต์แวร์ที่ปฏิบัติตามมาตรฐานจะยอมทำตาม ในขณะที่ซอฟต์แวร์ที่ไม่หวังดีจะไม่ทำตาม หากปนสองชั้นนี้เข้าด้วยกัน สิ่งที่ส่งมอบออกไปจะรู้สึกปลอดภัยตอนสาธิต แต่จะรั่วไหลเมื่อใช้งานจริง
HotPDF เป็น native VCL PDF component สำหรับ Delphi และ C++Builder และมันเปิดให้ใช้โมเดลการป้องกันตาม ISO 32000 ผ่าน property ชุดเล็ก ๆ ตัว property เองตั้งค่าได้ง่าย ส่วนที่ยากคือการรู้ว่าตัวไหนให้การป้องกันเชิง cryptographic จริง ๆ และตัวไหนเป็นเพียงคำแนะนำอย่างสุภาพเท่านั้น รวมถึงการเรียงลำดับการกำหนดค่าให้ถูกต้อง เพื่อให้การเข้ารหัสที่ขอไว้เป็นการเข้ารหัสที่ได้จริง ๆ
สิ่งที่รหัสผ่านทั้งสองแบบรับประกันได้จริง ๆ
การเข้ารหัสของ PDF กำหนดข้อมูลรับรองไว้สองแบบที่มีหน้าที่ต่างกัน และการปนสองสิ่งนี้เข้าด้วยกันคือข้อผิดพลาดด้านการออกแบบที่พบบ่อยที่สุดในโค้ดที่ป้องกันผลลัพธ์ user password เป็นตัวควบคุมการถอดรหัส หากไม่มีมัน หรือไม่มี owner password ผู้อ่านที่ปฏิบัติตามมาตรฐานจะไม่สามารถสร้าง file key ขึ้นมาใหม่ได้ และเนื้อหาก็จะยังคงอ่านไม่ได้ในเชิง cryptographic ส่วน owner password จะควบคุมการตั้งค่าสิทธิ์อนุญาตแทน: ผู้อ่านที่ได้รับ owner password จะได้รับสิทธิ์เข้าถึงเต็มรูปแบบไม่ว่า flag จำกัดสิทธิ์จะระบุไว้อย่างไรก็ตาม
permission bit ยืนอยู่บนพื้นฐานที่อ่อนแอกว่า การพิมพ์ การดึงเนื้อหา การกรอกฟอร์ม: แต่ละอย่างเป็นเพียง flag ที่ viewer อ่านแล้วเลือกที่จะเคารพหรือไม่ (ISO 32000-2 §7.6.4) การเข้ารหัสปกป้องตัวไบต์จริง ๆ ส่วน permission flag เป็นเพียงคำสั่งสำหรับซอฟต์แวร์ที่ปฏิบัติตามมาตรฐานเท่านั้น และมันสั่งการหลังจากเหตุการณ์เกิดขึ้นไปแล้ว ใครก็ตามที่เปิดเอกสารด้วย user password ก็ถือเนื้อหาที่ถอดรหัสแล้วไว้ในหน่วยความจำอยู่แล้ว ดังนั้น "ห้ามคัดลอก" และ "ห้ามพิมพ์" จึงมีความหมายสำหรับ viewer ที่ประพฤติตัวดี แต่ไม่มีความหมายเลยสำหรับผู้ที่ตั้งใจจะฝ่าฝืน ให้สร้าง threat model โดยยึดเส้นแบ่งนี้เป็นหลัก ความลับอยู่ที่ user password ส่วน permission กำหนดรูปแบบสิ่งที่ viewer กระแสหลักนำเสนอ และนั่นคือทั้งหมดที่มันทำได้
ลำดับการตั้งค่า: ทุกอย่างต้องมาก่อน BeginDoc
HotPDF จะสร้าง encryption dictionary และคำนวณ file key ในจังหวะที่ BeginDoc ทำงาน ไม่ว่า property การป้องกันจะมีค่าอะไรอยู่ ณ ขณะนั้น นั่นคือสิ่งที่เอกสารจะได้รับ และการเปลี่ยนค่ามันหลังจากนั้นจะไม่มีผลอะไรเลย property ที่สำคัญที่สุดในที่นี้คือ CryptKeyLength ซึ่งเลือก scheme จากค่าของ THPDFKeyType ได้แก่ k40, k128, aes128 และ aes256 ถ้ากำหนดค่าให้มันหลัง BeginDoc จะไม่มี exception ไม่มีคำเตือนใด ๆ มีเพียงไฟล์ที่เงียบ ๆ คงค่าที่มันเริ่มต้นไว้เท่านั้น ความคลาดเคลื่อนแบบเงียบ ๆ ชนิดนี้เลวร้ายที่สุด: มันผ่านการทดสอบในเครื่องทุกครั้ง แล้วโผล่มาเป็นปัญหา compliance บนโต๊ะลูกค้าอีกหลายเดือนให้หลัง
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'statement.pdf';
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256; // ต้องกำหนดก่อน BeginDoc
Pdf.UserPassword := 'open-secret';
Pdf.OwnerPassword := 'admin-secret';
Pdf.UseAES256R6 := False; // R=5: รองรับ viewer ได้กว้างที่สุด
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, 'Account statement, June 2026');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
รหัสผ่านเป็น UTF-8 และถูกจำกัดไว้ที่ 127 ไบต์ ซึ่งเป็นขีดจำกัดตาม ISO 32000-2 สำหรับ scheme แบบ AES-256 ถ้านโยบายรหัสผ่านของคุณส่งรหัสลับที่ยาวกว่านั้นมาให้ ให้ตัดความยาวเองที่ฝั่งของคุณ ในจุดที่คุณควบคุมได้แน่นอนว่าจะตัดตรงไหน ถ้าปล่อยให้เป็นไปโดยบังเอิญ ตัวไลบรารีกับ viewer บางตัวในอนาคตอาจไม่ลงรอยกันเรื่องจุดตัด ซึ่งจะทำให้ได้ไฟล์ที่เปิดได้ในเครื่องคุณ แต่ปฏิเสธรหัสผ่านเดียวกันเมื่อไปเปิดที่อื่น
Revision 5 หรือ revision 6: บูลีนตัวเดียว สองระบบนิเวศ
UseAES256R6 เป็นตัวเลือกระหว่างการจับมือแบบ AES-256 สองแบบ และตัวเลือกนี้ส่งผลกระทบมากกว่าที่ชนิดข้อมูลบูลีนของมันจะบ่งบอก ถ้าปล่อยไว้เป็น False HotPDF จะเขียน revision 5 ซึ่งเป็น scheme AES-256 ที่มาในรูปแบบส่วนขยายของ PDF 1.7 และ viewer มาราวสิบห้าปีก็เปิดได้ ถ้าตั้งเป็น True จะได้ revision 6 ซึ่งเป็นการคำนวณ key ที่แข็งแกร่งขึ้นตามมาตรฐาน ISO 32000-2 สำหรับ PDF 2.0 ที่ปิดจุดอ่อนที่ทราบกันดีในวิธีที่ revision 5 ตรวจสอบรหัสผ่าน
ดังนั้นในเชิง cryptographic revision 6 จึงเป็นตัวเลือกที่ดีกว่า แต่ก็เป็นตัวที่ทำให้สิ่งต่าง ๆ พังได้เช่นกัน ไฟล์ revision 6 ต้องการ viewer ที่สร้างมาสำหรับ PDF 1.7 Extension Level 3 หรือ PDF 2.0 และซอฟต์แวร์ที่ใช้งานอยู่จริงจำนวนมากไม่ใช่ทั้งสองอย่างนั้น: คลัง records-management, renderer ที่ฝังอยู่ในผลิตภัณฑ์อื่น ๆ, เครื่องมือ line-of-business ที่ไม่มีใครแตะต้องมานานหลายปี พวกนี้จะปฏิเสธไฟล์ไปเลยทันที และมันจะเกิดขึ้นบนเครื่องของลูกค้า ไม่ใช่บนเครื่องคุณ ค่าเริ่มต้นที่ใช้งานได้จริงจึงคือ revision 5 ให้เลือกใช้ revision 6 ก็ต่อเมื่อนโยบายความปลอดภัยระบุ ISO 32000-2 โดยเจาะจง revision และเมื่อคุณยืนยันแล้วจริง ๆ ว่าผู้บริโภคทุกรายอ่านมันได้ ไม่ว่าจะเลือกแบบไหน ให้จดบันทึกไว้ว่าเลือกตัวไหนและเพราะอะไร เพราะคนถัดไปที่มาแตะโค้ดนี้จะสงสัย
key type รุ่นเก่าสมควรได้รับคำอธิบายสักประโยคเพื่อให้รู้ว่าควรข้ามมันไป THPDFKeyType ยังคงมีรายการ k40, k128 และ aes128 อยู่ แต่มันมีไว้เพื่อสร้างคลังเอกสารเก่าขึ้นใหม่เท่านั้น ไม่ใช่ไว้ป้องกันเอกสารใหม่ RC4 ขนาด 40 บิตพ่ายแพ้ต่อฮาร์ดแวร์ทั่วไปได้ง่าย ๆ และ scheme ขนาด 128 บิตก็มีมาก่อน revision ของ AES-256 ที่การตรวจสอบความปลอดภัยยุคปัจจุบันจะคาดหวัง สำหรับเอกสารที่คุณกำลังสร้างในปี 2026 คำถามที่แท้จริงมีแค่ revision 5 กับ revision 6 เท่านั้น ถ้าพบว่าตัวเองกำลังจะหยิบ key type รุ่นเก่ามาใช้กับงานออกแบบใหม่ แสดงว่ามีบางอย่างผิดพลาดไปตั้งแต่ต้นทางแล้ว
Permission flag แบบไม่มีรหัสผ่านเปิดไฟล์
บ่อยครั้งความต้องการกลับตรงข้ามกับการปกปิด นั่นคือใคร ๆ ก็ควรจะอ่านเอกสารได้ แต่การพิมพ์หรือการดึงเนื้อหาถูกจำกัดไว้ คุณแสดงเจตนานี้ได้ด้วยการปล่อย user password ว่างเปล่าและกำหนด owner password ที่ไม่ว่าง ซึ่ง PDF เรียกโหมดนี้ว่า open-password mode และคุณระบุรายการปฏิบัติการที่อนุญาตไว้ใน ProtectOptions
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := ''; // ใครก็เปิดไฟล์นี้ได้
Pdf.OwnerPassword := 'rotate-me-quarterly'; // ป้องกันชุด permission
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... เนื้อหาของหน้า ...
Pdf.EndDoc;
set ของ THPDFProtectOptions จับคู่กับ permission bit ตาม ISO ได้แก่ prPrint และ prPrint12bit สำหรับการพิมพ์ความละเอียดสูง prInformationCopy สำหรับการคัดลอกและดึงเนื้อหาทั่วไป prExtractContent สำหรับการดึงเนื้อหาโดยเทคโนโลยีช่วยเหลือ รวมถึง prModifyStructure, prEditAnnotations, prFillAnnotations และ prAssemble มีสองตัวที่สมควรได้รับคำเตือนเป็นพิเศษ ให้เปิด prExtractContent ไว้แทบทุกโปรไฟล์ที่คุณสร้าง เพราะมันคือ bit ที่ screen reader ต้องใช้เพื่อเข้าถึงข้อความ และการปิดมันเงียบ ๆ จะเปลี่ยนการตัดสินใจด้านสิทธิ์ให้กลายเป็นข้อบกพร่องด้าน accessibility ที่ผู้พิการจะเจอ แต่คุณจะไม่มีวันเห็นเอง กับดักอีกอย่างคือการเปิด prPrint เพียงลำพังโดยไม่มี prPrint12bit: viewer หลายตัวจะตอบสนองด้วยการลดคุณภาพการพิมพ์ลง และผู้ใช้ของคุณจะแจ้งเรื่องนี้เป็นบั๊กด้านการแสดงผล ทั้งที่จริง ๆ แล้วมันคือการตั้งค่า permission ที่ทำงานตามที่ควรจะเป็น
การตรวจสอบใช้เวลาแค่ห้านาทีและควรอยู่ใน release checklist ของคุณ ให้เปิดตัวอย่างของแต่ละโปรไฟล์ใน Acrobat เปิด Document Properties แล้วอ่านแท็บ Security ซึ่งจะระบุอัลกอริทึมไว้ชัดเจน ("AES 256-bit") และแสดงรายการปฏิบัติการที่อนุญาตทีละรายการ จากนั้นเปิดไฟล์เดียวกันนั้นใน viewer รุ่นเก่าที่สุดที่ลูกค้าของคุณใช้งานจริง ไม่ใช่รุ่นล่าสุดในเครื่องของคุณ การเปิดครั้งที่สองนี้คือประกันราคาถูกที่ป้องกันไม่ให้ไฟล์ revision 6 ผ่านช่วง development ไปได้สวยแล้วไปพังที่ลูกค้าที่ไม่เคยอัปเดตเลย
การถอดการป้องกันออกจากไฟล์ที่มีอยู่แล้ว
การถอดรหัสก็คือการรัน property model แบบเดิมย้อนกลับ โหลดเอกสารด้วยข้อมูลรับรองที่ถูกต้อง ปิดการป้องกัน แล้วบันทึกผลลัพธ์ออกมาโดยไม่มีการป้องกันนั้นอีก
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('encrypted.pdf', 'open-secret');
if PageCount > 0 then
begin
Pdf.ActivateProtection := False; // ตัดการเข้ารหัสออกตอนบันทึก
Pdf.SaveLoadedDocument('plain.pdf');
end;
finally
Pdf.Free;
end;
end;
วิธีนั้นจะ parse เอกสารทั้งฉบับเข้าหน่วยความจำ ซึ่งใช้ได้ดีกับไฟล์ทั่วไปแต่สิ้นเปลืองสำหรับไฟล์ขนาดใหญ่มาก เมื่ออินพุตมีขนาดหลายร้อยเมกะไบต์ DecryptFile เป็นทางเลือกที่ประหยัดกว่า: มันถอดรหัสระหว่างการคัดลอกระดับไฟล์ โดยใช้เส้นทางเขียนใหม่แบบ AES-256 โดยตรงที่ข้ามการสร้าง object tree แบบเต็มรูปแบบทุกครั้งที่อินพุตเอื้ออำนวย มันเป็นส่วนหนึ่งของ Direct File API ที่กล่าวถึงในบทความคู่กันเรื่องการประมวลผล PDF ขนาดใหญ่จาก Delphi
ข้อจำกัดที่มีปฏิสัมพันธ์กับการเข้ารหัส
มีข้อจำกัดสองอย่างที่ควรรู้ก่อนที่จะออกแบบรอบ ๆ การเข้ารหัส ไม่ใช่รู้ทีหลัง ข้อแรกคือความสอดคล้องด้านการเก็บถาวร ISO 19005 ห้ามการเข้ารหัสใน PDF/A ดังนั้น workflow ใดก็ตามที่เข้ารหัสเอกสารและในขณะเดียวกันก็อ้างว่าเป็นไปตามPDF/A conformanceจึงขัดแย้งกันในตัวมันเองโดยพื้นฐาน HotPDF จะไม่ยอมให้คุณมีทั้งสองอย่างในไฟล์เดียวกัน เมื่อคุณต้องการทั้งสองอย่างจริง ๆ คำตอบคือทำสองไฟล์แยกกัน: สำเนาที่เข้ารหัสไว้สำหรับแจกจ่าย และสำเนาที่ไม่เข้ารหัสแยกต่างหากไว้สำหรับเก็บถาวร
ข้อจำกัดที่สองนั้นชัดเจนยิ่งกว่า การเข้ารหัส PDF ไม่มีระบบ escrow และไม่มีการกู้คืนใด ๆ หากทำ user password ของไฟล์ R5 หรือ R6 หาย ทางเลือกที่เหลือมีเพียง brute force หรือยอมแพ้เท่านั้น ดังนั้นให้ปฏิบัติต่อ owner secret และ user secret เหมือนกับที่คุณปฏิบัติต่อข้อมูลรับรองในระบบ production ทั่วไป สร้างมันขึ้นมา เก็บไว้ใน vault หมุนเวียนเปลี่ยนตามตารางเวลา สิ่งเดียวที่ห้ามทำเด็ดขาดคือฝังมันเป็นค่าคงที่ตายตัวไว้ในยูนิต ซึ่งจะพามันเข้าไปอยู่ใน version control โดยตรงและติดอยู่ในสำเนางานของนักพัฒนาทุกคนตลอดไป
มีอีกหนึ่งความเคยชินสุดท้ายที่ควรสร้างขึ้นมา การเปลี่ยนการป้องกันของไฟล์ที่คุณไม่ได้เป็นผู้สร้างเองนั้นใช้กลไกเดียวกันกับการถอดรหัส ไม่ใช่ฟีเจอร์แยกต่างหาก: โหลดไฟล์พร้อมรหัสผ่านผ่าน LoadFromFile แก้ไข ProtectOptions หรือรหัสผ่านตรงจุดนั้น แล้วเขียนกลับด้วย SaveLoadedDocument ถ้าคุณถอดรหัสไฟล์ได้ คุณก็เปลี่ยน permission ของมันใหม่ได้เช่นกัน และโค้ดก็แทบจะเหมือนกับตัวอย่างข้างต้นทุกประการ
property การป้องกันที่แสดงในบทความนี้เป็นส่วนหนึ่งของHotPDF Delphi Componentมาตรฐานสำหรับ Delphi และ C++Builder หน้าผลิตภัณฑ์มีเอกสารอ้างอิงด้านการเข้ารหัสฉบับเต็ม รวมถึง enumeration ของ permission แบบครบถ้วน