0

0

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

WBOY

WBOY

发布时间:2024-02-09 10:00:11

|

810人浏览过

|

来源于stackoverflow

转载

使用 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

立即学习go语言免费学习笔记(深入)”;

我的完整代码是:

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 行来检查这一点。

MagickPen
MagickPen

在线AI英语写作助手,像魔术师一样在几秒钟内写出任何东西。

下载

您当前正在使用 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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

182

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

229

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

343

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

394

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

220

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

193

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

418

2025.06.17

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

1

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 5万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

Go 教程
Go 教程

共32课时 | 4.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号