HotPDF ตรวจสอบลายเซ็นดิจิทัลในเอกสาร PDF ที่โหลดขึ้นมาผ่านสามเมธอดของ THotPDF ได้แก่: GetLoadedSignatureInfo, VerifyLoadedSignature และ VerifyLoadedSignatureEx ซึ่งเพิ่มเข้ามาในเวอร์ชัน v2.259.0 คอมโพเนนต์จะคำนวณแฮชของส่วน /ByteRange ของไฟล์ดั้งเดิมใหม่ ตรวจสอบแอตทริบิวต์ messageDigest ของ CMS และเรียกใช้การตรวจสอบ RSA PKCS#1 v1.5 เทียบกับใบรับรองผู้ลงนามที่ฝังตัวอยู่ โดยจะส่งกลับค่า svValid เมื่อข้อมูลไบต์ของเอกสารไม่มีการเปลี่ยนแปลง
สถานการณ์ดังกล่าวนับเป็นเรื่องปกติทั่วไปแต่มีความสำคัญอย่างมาก เมื่อคู่สัญญาส่งสัญญากลับคืนมาพร้อมลงลายเซ็น ระบบการทำงานของคุณต้องบันทึกไฟล์นั้นเก็บเข้าแฟ้มข้อมูล และมีคนถามคำถามสำคัญเพียงคำถามเดียวคือ: เอกสารนี้คือตัวเดียวกับที่เราส่งไปอย่างแม่นยำทุกไบต์ และลงลายเซ็นด้วยใบรับรองที่อ้างสิทธิ์จริงหรือไม่? การตอบคำถามนี้ด้วยรหัสโปรแกรมคือการตรวจสอบความถูกต้องของลายเซ็น ส่วนฝั่งการลงลายเซ็น การสร้างและการฝังลายเซ็น PAdES ตั้งแต่แรก จะอธิบายไว้ในบทความคู่ขนานเกี่ยวกับการสร้างลายเซ็นดิจิทัล PAdES ด้วย HotPDF บทความนี้จะเน้นไปที่ทิศทางตรงกันข้าม: คือเมื่อได้รับไฟล์ PDF ที่ลงลายเซ็นไว้เรียบร้อยแล้ว และคุณต้องการคำตัดสินแบบโปรแกรม แทนที่จะเป็นการจับภาพหน้าจอกล่องเครื่องหมายถูกสีเขียวของ Acrobat
PDF ที่ลงลายเซ็นจะพิสูจน์ได้อย่างไรว่าไม่มีการดัดแปลงข้อมูล?
ลายเซ็น PDF จะคุ้มครองช่วงไบต์ข้อมูลที่ระบุเฉพาะของไฟล์ ไม่ใช่ตามความเข้าใจที่เป็นนามธรรมของ "เอกสาร" ISO 32000-1 §12.8 กำหนดกลไกการทำงานไว้ดังนี้: ฟิลด์แบบฟอร์มลายเซ็นจะมีพจนานุกรมซึ่งข้อมูลรายการ /Contents จะเก็บคอนเทนเนอร์ CMS SignedData (RFC 5652) และอาร์เรย์ /ByteRange จะระบุพื้นที่พิกัดที่แท้จริงของไฟล์ที่ลายเซ็นครอบคลุมตาม §12.8.1 อาร์เรย์นี้จะเป็นรายการของคู่ออฟเซ็ตและความยาว ซึ่งในทางปฏิบัติจะแบ่งเป็นสองส่วน ได้แก่ ทุกสิ่งทุกอย่างที่อยู่ก่อนข้อความฐานสิบหก /Contents และทุกสิ่งทุกอย่างหลังข้อความนั้น ค่าของลายเซ็นไม่สามารถครอบคลุมตัวเองได้ ดังนั้นไฟล์จึงถูกนำไปเข้ารหัสแฮชโดยเว้นส่วนช่องว่างดังกล่าวไว้
การออกแบบนี้ส่งผลต่อการเขียนโครงสร้าง API ทั้งหมด: ขั้นตอนการตรวจสอบความถูกต้องจำเป็นต้องคำนวณแฮชจากข้อมูลไบต์ที่จัดเรียงลำดับเดิม ซึ่งวางอยู่บนดิสก์จริง การประเมินเทียบกับแบบจำลองออบเจกต์ที่วิเคราะห์มาจะไม่มีประโยชน์สำหรับกระบวนการนี้ เนื่องจากแม้การเขียนข้อมูลอิงตามเอกสารที่ไม่เปลี่ยนแปลงก็สามารถสร้างค่าไบต์ที่ต่างไปจากเดิมได้ HotPDF จึงใช้การตรวจสอบเทียบกับไฟล์ต้นฉบับที่ถูกโหลดขึ้นมา หรือตรวจสอบเทียบกับ TStream ของไบต์ดิบที่คุณป้อนให้ และจะไม่มีวันตรวจสอบกับโครงสร้างที่จัดเก็บอยู่ในหน่วยความจำโดยตรง
การอ่านข้อมูลเมทาดาตาของลายเซ็นก่อนการตรวจสอบความถูกต้อง
ฟังก์ชัน GetLoadedSignatureInfo จะวิเคราะห์พจนานุกรมลายเซ็นและคอนเทนเนอร์ CMS โดยไม่แตะต้องข้อมูลไบต์ของเอกสารเลย ซึ่งทำให้มันเป็นฟังก์ชันตัวแรกที่ควรเรียกใช้เมื่อคุณต้องการเพียงแค่แสดงผลว่าใครลงชื่อและลงเวลาเมื่อใด ฟิลด์ลายเซ็นจะมีดัชนีเริ่มต้นจาก 0 เรียงตามลำดับฟิลด์แบบฟอร์ม และ GetLoadedSignatureFieldCount จะบอกคุณว่ามีฟิลด์ลายเซ็นอยู่ทั้งหมดกี่ฟิลด์ เรกคอร์ด THPDFSignatureInfo ที่ส่งกลับมาจะเก็บชื่อฟิลด์, /SubFilter, ชื่อสามัญของใบรับรองผู้ลงนาม (common name), ชื่อเฉพาะของผู้ลงชื่อและผู้ออกใบรับรอง (subject and issuer DN), หมายเลขซีเรียล, วันเวลาใช้งาน, เวลาลงชื่อ (ดึงจากแอตทริบิวต์ที่ลงนามไว้หากมี หรือข้อมูลรายการ /M ของพจนานุกรม), ชื่ออัลกอริทึมสรุปข้อมูล และสตริง /Reason, /Location และ /ContactInfo โดยตัวแปร Status ของข้อมูลจะรายงานเป็น svNotVerified ซึ่งเป็นสถานะบ่งชี้ว่า "วิเคราะห์แล้ว แต่ยังไม่ได้ตรวจสอบความถูกต้อง"
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('signed-contract.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Info := Pdf.GetLoadedSignatureInfo(I);
Writeln('ฟิลด์: ', Info.FieldName);
Writeln('ผู้ลงนาม: ', Info.SignerName);
Writeln('ผู้ออกใบรับรอง: ', Info.IssuerDN);
Writeln('อัลกอริทึม: ', Info.HashAlgorithm);
Writeln('ตัวกรองย่อย: ', Info.SubFilter);
end;
finally
Pdf.Free;
end;
end;
การดำเนินการตรวจสอบความถูกต้องด้วยการเข้ารหัสลับ
ฟังก์ชัน VerifyLoadedSignatureEx จะทำการตรวจสอบความถูกต้องทั้งหมดสำหรับเอกสารที่โหลดจากไฟล์ และส่งเรกคอร์ดข้อมูลกลับมาในการเรียกใช้เพียงครั้งเดียว: มันจะทำการเปิดไฟล์ต้นฉบับอีกครั้ง เข้ารหัสแฮชส่วน /ByteRange ด้วยอัลกอริทึมสรุปข้อมูล SignerInfo นำผลลัพธ์ที่ได้ไปเปรียบเทียบกับแอตทริบิวต์ messageDigest ที่ลงนามไว้ (ตาม RFC 5652 §5.4) จากนั้นดำเนินการตรวจสอบลายเซ็น RSA กับการเข้ารหัส DER SET ซ้ำของแอตทริบิวต์ที่ลงนามแล้ว เมื่อลายเซ็นไม่มีแอตทริบิวต์ที่ลงนาม การตรวจสอบ RSA จะทำงานตรงกับแฮชของเอกสารแทน ลายเซ็นที่ระบบรองรับได้แก่ RSA PKCS#1 v1.5 ร่วมกับรหัสสรุปข้อมูล SHA-1, SHA-256, SHA-384 หรือ SHA-512 ซึ่งครอบคลุมตัวกรองย่อย adbe.pkcs7.detached และ ETSI.CAdES.detached ที่สร้างขึ้นด้วยเครื่องมือลงชื่อทั่วไป
var
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
Status := Pdf.VerifyLoadedSignatureEx(0, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln('ถูกต้อง; ลายเซ็นครอบคลุมทั้งไฟล์')
else
Writeln('ถูกต้อง; ไฟล์ถูกขยายหลังจากการลงชื่อ');
svDigestMismatch:
Writeln('ข้อมูลไบต์ของเอกสารเปลี่ยนไปหลังการลงชื่อ');
svSignatureInvalid:
Writeln('การตรวจสอบ RSA ล้มเหลวกับแอตทริบิวต์ที่ลงชื่อ');
svUnsupportedAlgorithm:
Writeln('คีย์ที่ไม่ใช่ RSA หรือไม่รู้จักอัลกอริทึมสรุปข้อมูล');
svMalformed:
Writeln('ไม่สามารถวิเคราะห์โครงสร้างคอนเทนเนอร์ CMS ได้');
svSourceUnavailable:
Writeln('ไม่มีข้อมูลไบต์ต้นทาง; โปรดเรียกใช้โอเวอร์โหลด TStream');
end;
end;
มีรายละเอียดการติดตั้งสองประการที่คุ้มค่าแก่การศึกษา เนื่องจากช่วยอธิบายความล้มเหลวที่ภายนอกอาจมองดูคล้ายเรื่องลึกลับ ประการแรก การตรวจสอบแอตทริบิวต์ที่เซ็นชื่อจะมีความเข้มงวดกับโครงสร้างการเข้ารหัสมาก: ภายในไฟล์แอตทริบิวต์จะถูกทำแท็กเป็น [0] IMPLICIT แต่ลายเซ็นจะคำนวณจากรูปแบบ DER SET OF ตัวตรวจสอบจึงต้องจัดทำแท็กใหม่ก่อนคำนวณแฮชตามที่มาตรฐาน RFC 5652 §5.4 กำหนดไว้ หากตัวตรวจสอบทั่วไปคำนวณแฮชจากไบต์ที่ปรากฏในไฟล์ทันทีจะปฏิเสธทุกเอกสารที่ลงลายเซ็นอย่างถูกต้อง ประการที่สอง /Contents มักจะถูกเติมด้วยเลขศูนย์ (zero-padded) ตามขนาดไบต์ที่จัดสรรไว้ล่วงหน้า ตัวตรวจสอบจึงต้องตัดบล็อบ DER ให้เหลือเฉพาะความยาวจริงของ SEQUENCE ด้านนอกก่อนจะประมวลผลวิเคราะห์ เลขศูนย์ที่ดูคล้ายขยะที่ส่วนท้ายจึงเป็นเรื่องปกติ ไม่ใช่รอยเสียหาย หัวข้ออันตรายของตัววิเคราะห์ไวยากรณ์ ASN.1 ในทำนองเดียวกันในฝั่งการนำเข้าใบรับรอง ได้อธิบายไว้ในบทความเกี่ยวกับ PKCS#12 และการรักษาความปลอดภัยของ ASN.1 ใน HotPDF
ลายเซ็นที่ถูกต้องรับประกันเรื่องใดได้บ้าง?
สถานะ svValid หมายความตามนี้โดยตรง: ข้อมูลไบต์ที่ระบุในช่วง /ByteRange จะแฮชได้ตรงตามค่าที่ผู้ลงชื่อได้ลงนามไว้ และลายเซ็นสามารถตรวจสอบผ่านคีย์สาธารณะ (public key) ของใบรับรองที่ฝังมาในคอนเทนเนอร์ CMS ได้ ซึ่งนั่นคือการรับประกันความครบถ้วนสมบูรณ์ของข้อมูลไบต์รวมถึงความเชื่อมโยงกับคีย์เท่านั้น โดยไม่มีการรับประกันอื่นใดเพิ่มเติม การตรวจสอบความสัมพันธ์ใบรับรองและความน่าเชื่อถือจะอยู่นอกเหนือขอบเขตการทำงานของตัวตรวจสอบใน HotPDF: มันจะไม่ทำการไล่ห่วงโซ่ใบรับรองไปยังจุดสูงสุด ตรวจสอบการยกเลิก หรือปรึกษากับแหล่งเก็บข้อมูลใบรับรองใดๆ ใบรับรองที่เซ็นชื่อด้วยตัวเอง (self-signed) จากผู้บุกรุกที่แก้ไขเอกสารแล้วลงลายเซ็นใหม่จะถูกรายงานผลเป็น svValid เนื่องจากความถูกต้องสอดคล้องกันของสมการคณิตศาสตร์ภายใน ส่วนประเด็นที่ว่าผู้ลงชื่อคือบุคคลนั้นจริงหรือไม่ และเราควรไว้วางใจหรือไม่ เป็นการตัดสินใจเชิงนโยบายของส่วนงานชั้นนอกอื่น ไม่ว่าจะเป็นรายการใบรับรองที่องค์กรของคุณอนุญาต แหล่งเก็บใบรับรองของระบบ Windows หรือหน่วยงานผู้ให้บริการตรวจสอบใบรับรอง
แฟล็ก CoversWholeDocument จะช่วยเฝ้าระวังช่องโหว่ที่ละเอียดอ่อน ลายเซ็นจะปกป้องเฉพาะช่วง /ByteRange ของตัวมันเองเท่านั้น และกลไกการอัปเดตแบบเพิ่มหน่วยของ PDF อนุญาตให้แนบเนื้อหาเพิ่มเติมต่อท้ายลายเซ็นได้โดยไม่ทำให้ลายเซ็นเดิมเสียไป ซึ่งได้รับการออกแบบมาเพื่อรองรับกระบวนการทำงานที่มีผู้ลงลายเซ็นหลายคน แฟล็กนี้จะถูกคำนวณระหว่างขั้นตอนการตรวจสอบความถูกต้องและจะมีค่าเป็นจริงก็ต่อเมื่อพื้นที่ของสองส่วนหลักบวกกับช่องว่าง /Contents ครอบคลุมความยาวทั้งหมดของไฟล์ เมื่อผลลัพธ์เป็น svValid แต่แฟล็ก CoversWholeDocument มีค่าเป็นเท็จ แสดงว่าเวอร์ชันของข้อมูลที่ลงลายเซ็นไว้ยังคงปลอดภัย แต่มีข้อมูลส่วนอื่นเพิ่มเข้ามาภายหลัง และการเปลี่ยนแปลงในส่วนต่อเติมนั้นเป็นสิ่งที่คุณควรกำหนดนโยบายเวิร์กโฟลว์ว่าอนุญาตยอมรับได้หรือไม่
เอกสารที่โหลดจากสตรีมและการเข้ารหัสต้องการข้อมูลไบต์ต้นทางเฉพาะตัว
ฟังก์ชัน VerifyLoadedSignature และ VerifyLoadedSignatureEx แบบไม่มีพารามิเตอร์เพิ่มเติมขึ้นอยู่กับการจดจำของคอมโพเนนต์ว่าเอกสารมาจากไฟล์ใด เมื่อโหลดเอกสารจากสตรีมข้อมูล จะไม่มีชื่อไฟล์ที่จะเปิดขึ้นมาใหม่ พฤติกรรมนี้เกิดขึ้นเช่นเดียวกันกับเอกสารเข้ารหัสที่ผ่านการเปิดใหม่ด้วยรหัสผ่าน ซึ่งอธิบายเวิร์กโฟลว์ไว้ในบทความเกี่ยวกับการเข้ารหัส PDF ด้วย AES-256 ใน HotPDF ในทั้งสองกรณี การเรียกใช้ฟังก์ชันแบบพึ่งพาไฟล์จะส่งกลับสถานะ svSourceUnavailable แทนการคาดเดาข้อมูล ทางแก้ไขคือการใช้โอเวอร์โหลด TStream ซึ่งช่วยให้คุณส่งข้อมูลไบต์ดั้งเดิมจากแหล่งใดก็ตามที่คุณเก็บไว้ ไม่ว่าจะเป็นไฟล์ที่เปิดอยู่ บัฟเฟอร์ในหน่วยความจำ หรือข้อมูลดิบจากฐานข้อมูล
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// เอกสารที่โหลดจากสตรีม: คอมโพเนนต์ไม่ได้เก็บชื่อ
// ไฟล์ต้นทางไว้ โปรดจัดส่งข้อมูลไบต์ดั้งเดิมด้วยตัวเอง
Src := TFileStream.Create('signed-contract.pdf',
fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
if Status <> svValid then
Writeln('การตรวจสอบความถูกต้องล้มเหลว: ', Ord(Status));
finally
Src.Free;
end;
end;
การรายงานสิ่งที่คุณไม่สามารถตรวจสอบความถูกต้องได้
ตัวตรวจสอบที่รู้จักเฉพาะผลลัพธ์ "ถูกต้อง" และ "ไม่ถูกต้อง" อาจรายงานผลผิดพลาดกับเอกสารที่มันเพียงไม่สามารถทำความเข้าใจได้ โครงสร้างแจกแจงสถานะของระบบจึงแยกกรณีการทำงานต่างๆ เพื่อให้ส่วนแสดงผลสามารถนำไปแสดงผลต่างกัน svDigestMismatch หมายถึงข้อมูลไบต์ของเอกสารมีการแก้ไขหลังลงลายเซ็น ซึ่งเป็นสัญญาณของการดัดแปลงข้อมูลทั่วไป svSignatureInvalid หมายถึงข้อมูลไบต์คำนวณแฮชได้ถูกต้องแต่ขั้นตอนตรวจสอบ RSA ล้มเหลว ซึ่งบ่งชี้ว่าลายเซ็นชำรุดหรือถูกทำปลอมขึ้นมา svUnsupportedAlgorithm เป็นคำตอบตรงไปตรงมาสำหรับคีย์แบบ ECDSA หรืออัลกอริทึมสรุปข้อมูลที่ระบบไม่รู้จัก: ลายเซ็นนั้นอาจจะถูกต้องสมบูรณ์ดี แต่ HotPDF ไม่สามารถทำการตรวจสอบได้ และการแจ้งผลว่า "ไม่ถูกต้อง" จะส่งผลเสียต่อเอกสารที่ปกติ svMalformed จะแจ้งผลกับคอนเทนเนอร์ CMS ที่ไม่สามารถอ่านวิเคราะห์โครงสร้างได้ และสำหรับขั้นตอนการตรวจสอบแบบครอบคลุม ฟังก์ชัน VerifyAllLoadedSignatures จะส่งกลับค่าจริงก็ต่อเมื่อมีฟิลด์ลายเซ็นอยู่จริงอย่างน้อยหนึ่งฟิลด์ และทุกฟิลด์ตรวจสอบความถูกต้องได้เป็น svValid ทั้งหมด ซึ่งเป็นค่าบูลีนตัวเดียวที่สะดวกสำหรับไพป์ไลน์การบันทึกคลังข้อมูลที่จะปฏิเสธไฟล์ที่ไม่ได้ตามเกณฑ์นี้
การตรวจสอบลายเซ็น การลงลายเซ็นแบบ PAdES การเข้ารหัส AES-256 และ API แก้ไขเอกสารที่โหลดขึ้นมา ทั้งหมดมาพร้อมกับไลบรารี VCL แบบเนทีฟชุดเดียวกันสำหรับ Delphi และ C++Builder โดยไม่มีความจำเป็นต้องใช้ DLLs ภายนอก รายการความสามารถทั้งหมดและรุ่น IDE ที่ระบบรองรับมีรวบรวมไว้บนหน้าผลิตภัณฑ์ HotPDF Component