Golang如何提取PDF数字签名数据并通过根证书完成验签
Golang 提取PDF数字签名并完成根证书验签实现
你提供的C#实现基于iText的签名处理能力,Go生态可以用unipdf库实现完全对等的功能,不需要从零解析PDF签名结构,证书校验、PKCS7验签直接用Go标准库就能完成,不需要额外找第三方加密依赖。
依赖说明
- PDF签名解析:使用unipdf库,是目前Go生态对PDF数字签名规范支持最完整的实现,覆盖签名枚举、签名范围校验、PKCS7数据提取所有你C#代码用到的iText核心能力
- 证书与签名校验:直接使用Go标准库
crypto/x509、crypto/pkcs7即可,无需引入额外第三方加密组件
实现逻辑(完全对齐提供的C#代码流程)
- 读取根证书文件,解析为x509证书对象
- 加载目标PDF文件,初始化签名处理工具实例
- 枚举PDF内所有嵌入的数字签名字段
- 对单个签名依次做三项校验:
- 校验签名覆盖范围,确认签名是否作用于整个文档,排除签名后追加内容的情况
- 校验签名本身的完整性,确认文档签名后未被篡改
- 提取签名携带的证书链,用预先加载的根证书校验签名者证书的信任链,确认签名者身份受信任
参考实现代码
package main import ( "crypto/x509" "errors" "fmt" "os" "github.com/unidoc/unipdf/v3/model" "github.com/unidoc/unipdf/v3/model/sighandler" ) func Verify(pdfPath string, rootCertPath string) error { // 加载根证书 对齐C#中X509Certificate初始化逻辑 rootCertBytes, err := os.ReadFile(rootCertPath) if err != nil { return fmt.Errorf("读取根证书失败: %w", err) } rootCert, err := x509.ParseCertificate(rootCertBytes) if err != nil { return fmt.Errorf("解析根证书失败: %w", err) } // 打开PDF 初始化签名工具 对齐C#中PdfDocument、SignatureUtil初始化逻辑 f, err := os.Open(pdfPath) if err != nil { return fmt.Errorf("打开PDF文件失败: %w", err) } defer f.Close() pdfReader, err := model.NewPdfReader(f) if err != nil { return fmt.Errorf("解析PDF结构失败: %w", err) } signUtil, err := sighandler.NewPdfSignatureHandler(pdfReader) if err != nil { return fmt.Errorf("初始化签名处理工具失败: %w", err) } // 获取所有签名名称 对齐C#中GetSignatureNames逻辑 signNames, err := signUtil.GetSignatureNames() if err != nil { return fmt.Errorf("获取PDF内签名列表失败: %w", err) } for _, signName := range signNames { // 校验签名覆盖范围 对齐C#中SignatureCoversWholeDocument逻辑 coverWholeDoc, err := signUtil.SignatureCoversWholeDocument(signName) if err != nil { return fmt.Errorf("校验签名[%s]覆盖范围失败: %w", signName, err) } if !coverWholeDoc { fmt.Printf("警告:签名[%s]未覆盖整个文档,存在签名后追加内容的风险\n", signName) } // 读取PKCS7签名数据 校验签名完整性 对齐C#中ReadSignatureData、VerifySignatureIntegrityAndAuthenticity逻辑 pkcs7Sign, err := signUtil.ReadSignatureData(signName) if err != nil { return fmt.Errorf("读取签名[%s]数据失败: %w", signName, err) } integrityValid, err := pkcs7Sign.VerifyIntegrity() if err != nil { return fmt.Errorf("校验签名[%s]完整性失败: %w", signName, err) } if !integrityValid { fmt.Printf("警告:签名[%s]完整性校验失败,文档签名后已被篡改\n", signName) } // 提取签名证书链 用根证书校验信任链 对齐C#中GetCertificates、certs[0].Verify逻辑 certChain := pkcs7Sign.GetCertificates() if len(certChain) == 0 { return fmt.Errorf("签名[%s]未携带证书信息", signName) } signerCert := certChain[0] // 构造信任根池,加载传入的根证书 trustPool := x509.NewCertPool() trustPool.AddCert(rootCert) _, err = signerCert.Verify(x509.VerifyOptions{ Roots: trustPool, }) certTrusted := true if err != nil { certTrusted = false if errors.Is(err, x509.CertificateInvalidError{}) || errors.Is(err, x509.UnknownAuthorityError{}) { fmt.Printf("警告:签名[%s]证书信任链校验失败,签名证书并非指定根证书签发\n", signName) } else { return fmt.Errorf("校验签名[%s]证书链失败: %w", signName, err) } } fmt.Printf("签名[%s]校验结果:覆盖全文档=%v, 内容完整性有效=%v, 证书链受信任=%v\n", signName, coverWholeDoc, integrityValid, certTrusted) } return nil } func main() { // 调用示例:传入PDF路径、根证书路径 if err := Verify("target.pdf", "root.cer"); err != nil { panic(err) } }
补充说明
- 若需要校验带时间戳的签名,只需在读取PKCS7数据后增加时间戳有效性校验逻辑,unipdf已封装对应方法,不需要额外解析
- 若需要校验证书吊销状态,可在x509.VerifyOptions中配置CRL或OCSP校验参数,Go标准库原生支持该能力
- 注意unipdf用于商业场景需要符合其对应授权协议,个人非商用场景可免费使用
内容的提问来源于stack exchange,提问作者Andrey Osipov
相关产品推荐
相关产品推荐

