首頁  >  文章  >  後端開發  >  使用 digitalorus/pdfsign 在 Go (Golang) 中簽署 pdf 文件

使用 digitalorus/pdfsign 在 Go (Golang) 中簽署 pdf 文件

WBOY
WBOY轉載
2024-02-09 10:00:11597瀏覽

使用 digitalorus/pdfsign 在 Go (Golang) 中签署 pdf 文件

在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 行來檢查這一點。

您目前正在使用 gopkcs12.DecodeChain 返回私鑰,但它沒有實現 crypto.Signer 接口,因此會出現錯誤。您可能需要實作一個自訂的,但這是另一個問題。

概念:

ECDSA 代表橢圓曲線數位簽章演算法。它是一種用於數位簽章的公鑰加密演算法。請參閱 Go 標準庫文件和 NIST 網站以了解更多資訊。

NIST P-384:P-384 是美國國家標準與技術研究院 (NIST) 建議的橢圓曲線之一,金鑰長度為 384 位元。有關數位簽名和更多推薦的橢圓曲線的更多信息,請參閱 NIST 網站。我使用 P-384 作為工作解決方案。

第二步是使用Go标准库中的crypto/x509包通过其链生成器生成x509证书和证书链。这些是您在问题中寻求帮助的特定变量,但不属于您在错误消息中可以清楚看到的预期类型。只需遵循 lib 指令并使用 x509.Certificate.Verify() 就像我在工作解决方案中所做的那样,这将返回正确的类型 [][]*x509.Certificate。

请参阅 Go 标准库文档以获取更多信息。

第三步是打开输入 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

以上是使用 digitalorus/pdfsign 在 Go (Golang) 中簽署 pdf 文件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:stackoverflow.com。如有侵權,請聯絡admin@php.cn刪除