บล็อกนี้จะมาแนะนำวิธีการเขียนโปรแกรมถอดรหัสไฟล์ PDF ด้วยภาษา GO โดยเราจะมาเปรียบเทียบวิธีการใช้งานระหว่าง 2 ไลบรารี่ ได้แก่ pdfcpu และ QPDF เนื่องจากทั้งสองไลบรารี่นี้สามารถนำมาใช้กับโปรแกรมภาษา GO ได้และเป็นไลบรารี่ที่นำมาใช้งานได้ง่าย และได้รับความนิยมเป็นอย่างมาก
มารู้จักทั้งสองไลบรารี่นี้แบบคร่าวๆ กันก่อน
- pdfcpu เป็นไลบรารี่ที่เอาไว้ทำเกี่ยวกับเรื่อง pdf process ซึ่งจะสามารถสร้าง pdf และ เข้ารหัสถอดรหัส pdf ได้
- pdfcpu มีการให้เรียกใช้ทั้งแบบนำเข้า pkg และ cli (ติดตั้งโปรแกรมที่เครื่อง)
- pdfcpu รองรับ PDF จนถึงเวอร์ชัน 1.7
- QPDF เป็นไลบรารี่ที่เขียนด้วยภาษา C++ ซึ่งนักพัฒนาสามารถนำซอสโค้ดไปพัฒนาต่อยอดได้ หรือนำไฟล์ที่ build แล้วไปรันผ่าน cli (ติดตั้งโปรแกรมที่เครื่อง) ก็ได้เช่นกัน
- QPDF เป็นเครื่องมือในระบบ low-level ที่ใช้ทำงานกับโครงสร้างของไฟล์ PDF
ในการทดสอบครั้งนี้ เราจะทดสอบทั้งสองไลบรารี่นี้ ด้วยวิธีทั้งเรียกใช้งานแบบนำเข้า pkg สำหรับ pdfcpu และการเรียกใช้งานผ่าน command line สำหรับ pdfcpu และ QPDF
ตัวอย่างการเรียกใช้งานแต่ละไลบรารี่
ในไฟล์ lib/decrypt_pdf.go นำเข้าไลบราภายนอกดังนี้
package lib
import (
"fmt"
"os/exec"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
)
1. pdfcpu แบบนำเข้า pkg
func PdfCpuDecryptPassword(input string, password string, output string) (result string, error string) {
conf := pdfcpu.NewAESConfiguration(password, "", 256)
err := api.DecryptFile(input, output, conf)
if err != nil {
result = fmt.Sprint("PDFCPU (pkg) Unlock Error : ", err.Error())
error = err.Error()
return
} else {
result = fmt.Sprint("PDFCPU (pkg) Unlock Finished : ", output)
return
}
}
ฟังก์ชันการทำงานที่สำคัญคือ 2 บรรทัดนี้ การถอดรหัสจำเป็นจะต้องระบุวิธี ซึ่งวิธีมาตราฐานของ PDF ปัจจุบันจะเข้ารหัสด้วยวิธี AES 256ิbit
conf := pdfcpu.NewAESConfiguration(password, "", 256)
err := api.DecryptFile(input, output, conf)
2. pdfcpu แบบเรียกผ่าน cli
func PdfCpuDecryptPasswordByCLI(pdfcpu string, input string, password string, output string) (result string, error string) {
arg0 := "decrypt"
arg1 := "-opw"
cmd := exec.Command(pdfcpu, arg0, arg1, password, input, output)
stdout, err := cmd.Output()
if err != nil {
result = fmt.Sprint("PDFCPU Unlock Error : ", string(stdout))
error = err.Error()
return
} else {
result = fmt.Sprint("PDFCPU Unlock Finished : ", output)
return
}
}
คำสั่งที่สำคัญคือ พารามิเตอร์ arg0 และ arg1 ที่จะต้องใช้ตามแบบอย่างของ pdfcpu
3. QPDF แบบเรียกผ่าน cli
func QPdfDecryptPasswordByCLI(qpdf string, input string, password string, output string) (result string, error string) {
arg0 := "--password=" + password
arg1 := "--decrypt"
cmd := exec.Command(qpdf, arg0, arg1, input, output)
stdout, err := cmd.Output()
if err != nil {
result = fmt.Sprint("QPDF Unlock Error : ", string(stdout))
error = err.Error()
return
} else {
result = fmt.Sprint("QPDF Unlock Finished : ", output)
return
}
}
คำสั่งที่สำคัญคือ พารามิเตอร์ arg0 และ arg1 ที่จะต้องใช้ตามแบบอย่างของ QPDF
ดูไฟล์โค้ดการทดสอบได้ที่ https://github.com/adaydesign/go-decrypt-pdf (โค้ดตัวอย่างมีการทดลองกับไลบรารี่ UniPDF เพิ่มด้วย)
ทดสอบโดยรัน
go run main.go
ผลการทดสอบ
ทดสอบถอนรหัสไฟล์ขนาด 179kb บนระบบปฏิบัติการวินโดว์
ดัวยวิธีการถอดรหัส 3 วิธี จำนวน 10 ครั้งแล้วหาค่าเฉลี่ยของเวลาที่แต่ละไลบรารี่ใช้ในการประมวลผล ได้ผลการทดสอบดังนี้
lib | excution | time(ms) | pdf version(output) |
pdfcpu | pkg | 14.9964 | 1.7 |
pdfcpu | cli | 71.3979 | 1.7 |
QPDF | cli | 434.3817 | 1.6 |
จากตารางจะพบว่า pdfcpu ที่ใช้ด้วยวิธีการนำเข้าแบบ pkg จะใช้เวลาในการประมวลผลน้อยที่สุด
สรุป
การถอดรหัสไฟล์ PDF ด้วยไลบรารี่ทั้งสอง สามารถถอดรหัสฟล์ PDF ได้ ในส่วนที่นำเข้า package มาใช้งานในโปรเจคจะทำงานได้เร็วกว่า การติดตั้ง binary ไฟล์ไว้ที่เครื่องแล้วใช้ command line ไปเรียกใช้ และ ไลบรารี่ของ pdfcpu ยังมีข้อจำกัดอีกหนึ่งอย่างคือ รองรับไฟล์ PDF ทุกเวอร์ชันจนถึง เวอร์ชัน 1.7 เท่านั้น ซึ่งปัจจุบันเวอร์ชัน PDF เป็นเวอร์ชัน 2.0 ไปแล้ว
อีกหนึ่งไลบรารี่ ที่นิยมคือ UniPDF เป็นไลบรารี่ที่ใช้จัดการเกี่ยวกับงาน PDF ด้วยภาษา Go โดยเฉพาะ ข้อจำกัดคือ จะต้องลงทะเบียนก่อนใช้งาน และหากเป็นการใช้งานในเวอร์ชันฟรี จะใช้ได้จำนวนจำกัด (100 ครั้ง) แต่ประสิทธิภาพการทำงานนั้น สามารถถอดรหัสไฟล์ PDF โดยใช้เวลาเร็วกว่า pdfcpu เสียอีก โดย https://github.com/adaydesign/go-decrypt-pdf มีตัวอย่างการเรียกใช้งาน UniPDF และผลการทดลองเอาไว้เรียบร้อยแล้ว
func init() {
// To get your free API key for metered license, sign up on: https://cloud.unidoc.io
// Make sure to be using UniPDF v3.19.1 or newer for Metered API key support.
err := license.SetMeteredKey(`##### your unipdf key here #####`)
if err != nil {
fmt.Printf("ERROR: Failed to set metered key: %v\n", err)
fmt.Printf("Make sure to get a valid key from https://cloud.unidoc.io\n")
panic(err)
}
}
func main(){
input := "files/locked_doc.pdf"
password := "@1234"
output4 := "files/out_unipdf_pkg.pdf"
err = lib.UniPdfDecryptPassword(input, password, output4)
if err != nil {
fmt.Println(err)
}
}
หากเลือกใช้ UniPDF จะต้องทำการลงทะเบียนใช้งานและสร้าง KEY เพื่อยืนยันสิทธิการใช้งานที่ https://cloud.unidoc.io/#/signup เมื่อได้ KEY มาแล้วก็นำมาใส่ในโค้ดทดสอบที่บรรทัด `##### your unipdf key here #####` เพื่อทดสอบโปรแกรมต่อไป