在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
以上就是使用 digitalorus/pdfsign 在 Go (Golang) 中签署 pdf 文件的详细内容,更多请关注编程网其它相关文章!