在Go語言中簽署PDF文件是一項常見的需求,而使用digitalorus/pdfsign庫可以輕松實現這一功能。php小編柚子為您介紹該庫的使用方法。無論是在業務應用中還是在個人項目中,簽署PDF文件都是一個常見的操作。digitalorus/pdfsign庫提供了簡潔易用的接口,使得在Go語言中簽署PDF文件變得簡單快捷。通過本文,您將了解到如何在Go語言中使用digitalorus/pdfsign庫來完成PDF文件的簽署操作。讓我們一起來探索吧!
問題內容
在 go (golang) 中,我需要簽署 pdf 文檔,但與其他語言不同的是,沒有任何庫可以使工作變得更容易。我找到了幾個付費的,但它們不是一個選擇。
首先,我有一個 PKCS 證書 (.p12),我已經使用此包從中提取私鑰和 x509 證書:https://pkg.go.dev/software.sslmate.com/src/go -pkcs12
但是當我想簽署 pdf 文檔時,我陷入了困境,因為我不知道如何正確地將參數傳遞給執行此類操作的函數。使用的包是https://pkg.go.dev/github.com/digitorus/pdfsign
我的完整代碼是:
package main import ( "crypto" "fmt" "os" "time" "github.com/digitorus/pdf" "github.com/digitorus/pdfsign/revocation" "github.com/digitorus/pdfsign/sign" gopkcs12 "software.sslmate.com/src/go-pkcs12" ) func main() { certBytes, err := os.ReadFile("certificate.p12") if err != nil { fmt.Println(err) return } privateKey, certificate, chainCerts, err := gopkcs12.DecodeChain(certBytes, "MyPassword") if err != nil { fmt.Println(err) return } input_file, err := os.Open("input-file.pdf") if err != nil { fmt.Println(err) return } defer input_file.Close() output_file, err := os.Create("output-file.pdf") if err != nil { fmt.Println(err) return } defer output_file.Close() finfo, err := input_file.Stat() if err != nil { fmt.Println(err) return } size := finfo.Size() rdr, err := pdf.NewReader(input_file, size) if err != nil { fmt.Println(err) return } err = sign.Sign(input_file, output_file, rdr, size, sign.SignData{ Signature: sign.SignDataSignature{ Info: sign.SignDataSignatureInfo{ Name: "John Doe", Location: "Somewhere on the globe", Reason: "My season for siging this document", ContactInfo: "How you like", Date: time.Now().Local(), }, CertType: sign.CertificationSignature, DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms, }, Signer: privateKey, // crypto.Signer DigestAlgorithm: crypto.SHA256, // hash algorithm for the digest creation Certificate: certificate, // x509.Certificate CertificateChains: chainCerts, // x509.Certificate.Verify() TSA: sign.TSA{ URL: "https://freetsa.org/tsr", Username: "", Password: "", }, // The follow options are likely to change in a future release // // cache revocation data when bulk signing RevocationData: revocation.InfoArchival{}, // custom revocation lookup RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, }) if err != nil { fmt.Println(err) } else { fmt.Println("Signed PDF written to output.pdf") } }
登錄后復制
確切地說,它們是我的問題的Signer和CertificateChains參數。我不知道如何正確使用 privateKey 和 chainCerts 變量。
消息錯誤是:
無法使用 privateKey(interface{} 類型的變量)作為結構文字中的 crypto.Signer 值:interface{} 未實現 crypto.Signer(缺少 Public 方法)
無法使用 chainCertificates([]*x509.Certificate 類型的變量)作為結構體文字中的 [][]*x509.Certificate 值
我是這門語言的新手,所以我仍然不了解深入的概念和數據類型。
感謝您告訴我還應該做什么或缺少哪些步驟才能取得成功。或者如果有人知道我如何根據 pkcs 證書簽署 pdf。
解決方法
使用數字簽名對 PDF 進行簽名包括使用公鑰加密技術生成一對密鑰。私鑰用于加密與簽名相關的數據,只有簽名者才能訪問它,而公鑰則用于解密簽名數據以進行驗證,如果不是由受信任的證書頒發機構頒發,則所述公鑰證書必須將其添加到證書存儲中以使其受到信任。
在給定的示例中,此簽名數據存儲在名為 sign.SignData 的結構內,該結構是 pdfsign 庫的一部分,需要 x509 證書和實現 crypto.Signer 接口的簽名者。
第一步是使用 Go 標準庫中的 crypto/ecdsa 包生成一對密鑰。 GenerateKey 將把私鑰和公鑰保存到 privateKey 變量中。這個返回的 privateKey 變量實現了 crypto.Signer 接口,這是解決您當前面臨的問題所必需的。
您可以通過閱讀 ecdsa.go 代碼第 142 行來檢查這一點。
https://github.com/golang /go/blob/master/src/crypto/ecdsa/ecdsa.go
您當前正在使用 gopkcs12.DecodeChain 返回私鑰,但它沒有實現 crypto.Signer 接口,因此會出現錯誤。您可能需要實現一個自定義的,但這是另一個問題。
概念:
ECDSA 代表橢圓曲線數字簽名算法。它是一種用于數字簽名的公鑰加密算法。請參閱 Go 標準庫文檔和 NIST 網站了解更多信息。
https://pkg.go.dev/crypto/[電子郵件受保護]
https://www.php.cn/link/813b6a28cc4ac72d244266161e3e2eb4
NIST P-384:P-384 是美國國家標準與技術研究院 (NIST) 推薦的橢圓曲線之一,密鑰長度為 384 位。有關數字簽名和更多推薦的橢圓曲線的更多信息,請參閱 NIST 網站。我使用 P-384 作為工作解決方案。
https://nvlpubs.nist.gov/nistpubs /FIPS/NIST.FIPS.186-4.pdf
https://www.php.cn/link/73c66ed635c83ba1316b28524a31b12f
https://csrc.nist.gov/pubs/fips/186 -4/國際泳聯
第二步是使用Go標準庫中的crypto/x509包通過其鏈生成器生成x509證書和證書鏈。這些是您在問題中尋求幫助的特定變量,但不屬于您在錯誤消息中可以清楚看到的預期類型。只需遵循 lib 指令并使用 x509.Certificate.Verify() 就像我在工作解決方案中所做的那樣,這將返回正確的類型 [][]*x509.Certificate。
請參閱 Go 標準庫文檔以獲取更多信息。
https://www.php.cn/link/92d1e1eb1cd6f9fba3227870bb6d7f07
https://www.php.cn/link/54d2e69c08f60ae7c37f509f962c59a8
https://www.php.cn/link/6afd3a1bbb557f8e05f45ded7bf96836
第三步是打開輸入 pdf 文件并使用 Go 標準庫中的 os 包創建輸出 pdf 文件。
第四步實際上是使用 digitalorus/pdfsign 庫對 pdf 文件進行簽名。
這是我今天編碼和測試的一個有效解決方案,旨在讓您回到正軌,進行一些研究并根據您的需求進行修改:
package main import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "fmt" "github.com/digitorus/pdf" "github.com/digitorus/pdfsign/revocation" "github.com/digitorus/pdfsign/sign" "log" "math/big" "os" "time" ) func main() { // First step privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { panic(err) } // Second step x509RootCertificate := &x509.Certificate{ SerialNumber: big.NewInt(2023), Subject: pkix.Name{ Organization: []string{"Your Organization"}, Country: []string{"Your Country"}, Province: []string{"Your Province"}, Locality: []string{"Your Locality"}, StreetAddress: []string{"Your Street Address"}, PostalCode: []string{"Your Postal Code"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } rootCertificateBytes, err := x509.CreateCertificate(rand.Reader, x509RootCertificate, x509RootCertificate, &privateKey.PublicKey, privateKey) if err != nil { panic(err) } rootCertificate, err := x509.ParseCertificate(rootCertificateBytes) if err != nil { panic(err) } roots := x509.NewCertPool() roots.AddCert(rootCertificate) certificateChain, err := rootCertificate.Verify(x509.VerifyOptions{ Roots: roots, }) if err != nil { panic(err) } // Third step outputFile, err := os.Create("output.pdf") if err != nil { panic(err) } defer func(outputFile *os.File) { err = outputFile.Close() if err != nil { log.Println(err) } fmt.Println("output file closed") }(outputFile) inputFile, err := os.Open("input.pdf") if err != nil { panic(err) } defer func(inputFile *os.File) { err = inputFile.Close() if err != nil { log.Println(err) } fmt.Println("input file closed") }(inputFile) fileInfo, err := inputFile.Stat() if err != nil { panic(err) } size := fileInfo.Size() pdfReader, err := pdf.NewReader(inputFile, size) if err != nil { panic(err) } // Fourth step err = sign.Sign(inputFile, outputFile, pdfReader, size, sign.SignData{ Signature: sign.SignDataSignature{ Info: sign.SignDataSignatureInfo{ Name: "Your name", Location: "Your location", Reason: "Your reason", ContactInfo: "Your contact info", Date: time.Now().Local(), }, CertType: sign.CertificationSignature, DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms, }, Signer: privateKey, // crypto.Signer DigestAlgorithm: crypto.SHA256, // hash algorithm for the digest creation Certificate: rootCertificate, // x509.Certificate CertificateChains: certificateChain, // x509.Certificate.Verify() TSA: sign.TSA{ URL: "", Username: "", Password: "", }, // The follow options are likely to change in a future release // // cache revocation data when bulk signing RevocationData: revocation.InfoArchival{}, // custom revocation lookup RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, }) if err != nil { log.Println(err) } else { log.Println("pdf signed") } }
登錄后復制
結果:
go run main.go 2023/12/01 21:53:37 pdf signed input file closed output file closed
登錄后復制