求助:怎么使用 go 实现 pdf 的数字签名与校验

92 天前
 cdx

目前有业务需要给 pdf 合同进行电子签名,目前使用的是 https://github.com/digitorus/pdfsign 这个库,但是碰到一个问题,首先是能够正常签名,但是校验时出现了一些问题。

当对已经签名过的 pdf 文件在末尾添加几个随机字节,此时使用 pdfsign 去检测,是没有办法检测到该文件已经被篡改了,在 adobe reader 中是能够检测到该文件有问题。

这里不讨论公司是否拥有这个资质的问题

有几个需要帮助的地方:

  1. 目前市面上常见的 pdf 签名方式是否与 pdfsign 类似
  2. 怎么解决这个问题
  3. 是否有其他依赖库实现 pdf 的数字签名与校验

代码:

package main

import (
	"crypto"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"github.com/digitorus/pdf"
	"github.com/digitorus/pdfsign/revocation"
	"github.com/digitorus/pdfsign/sign"
	"github.com/digitorus/pdfsign/verify"
	"log"
	"os"
	"time"
)

func main() {
	err := run("a.pdf", "b.pdf")
	if err != nil {
		panic(err)
	}
	data, err := os.ReadFile("b.pdf")
	if err != nil {
		panic(err)
	}
	data = append(data, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}...)
	err = os.WriteFile("c.pdf", data, 0644)
	if err != nil {
		panic(err)
	}
	verifyPdf("c.pdf")
}


func verifyPdf(pdfName string) {
	input_file, err := os.Open(pdfName)
	if err != nil {
		panic(err)
	}
	defer input_file.Close()

	resp, err := verify.File(input_file)
	if err != nil {
		panic(err)
	}
	jsonData, err := json.MarshalIndent(resp, "", "\t")
	if err != nil {
		panic(err)
	}
	// 将 jsonData 的数据写入文件
	err = os.WriteFile("verify.json", jsonData, 0644)
	return
}

func run(input, output string) error {
	input_file, err := os.Open(input)
	if err != nil {
		panic(err)
	}
	defer input_file.Close()

	output_file, err := os.Create(output)
	if err != nil {
		panic(err)
	}
	defer output_file.Close()

	finfo, err := input_file.Stat()
	if err != nil {
		panic(err)
	}
	size := finfo.Size()

	rdr, err := pdf.NewReader(input_file, size)
	if err != nil {
		panic(err)
	}

	certificate_data, err := os.ReadFile("certificate.crt")
	if err != nil {
		panic(err)
	}
	certificate_data_block, _ := pem.Decode(certificate_data)
	if certificate_data_block == nil {
		//log.Fatal(errors.New("failed to parse PEM block containing the certificate"))
		panic(err)
	}

	cert, err := x509.ParseCertificate(certificate_data_block.Bytes)
	if err != nil {
		panic(err)
	}

	privateKeyFs, err := os.ReadFile("private_key.pem")
	if err != nil {
		panic(err)
	}
	key_data_block, _ := pem.Decode(privateKeyFs)
	if key_data_block == nil {
		panic(errors.New("failed to parse PEM block containing the private key"))
	}
	// 尝试解析 PKCS#1 格式的私钥
	pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes)
	if err != nil {
		var t any
		t, err = x509.ParsePKCS8PrivateKey(key_data_block.Bytes)
		pkey = t.(*rsa.PrivateKey)
		if err != nil {
			panic(err)
		}
	}

	certificate_chains := make([][]*x509.Certificate, 0)
	err = sign.Sign(input_file, output_file, rdr, size, sign.SignData{
		Signature: sign.SignDataSignature{
			Info: sign.SignDataSignatureInfo{
				Name:        "xx",
				Location:    "xx",
				Reason:      "xx",
				ContactInfo: "xxx",
				Date:        time.Now().Local(),
			},
			CertType:   sign.CertificationSignature,
			DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms,
		},
		Signer:            pkey,               // crypto.Signer
		DigestAlgorithm:   crypto.SHA256,      // hash algorithm for the digest creation
		Certificate:       cert,               // x509.Certificate
		CertificateChains: certificate_chains, // 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 {
		panic(err)
	} else {
		log.Println("Signed PDF written to " + output)
	}
	return nil
}

/*
自签私钥与证书生成
1. 生成私钥
openssl genpkey -algorithm RSA -out private_key.pem
2. 创建证书签名请求 (CSR)
openssl req -new -key private_key.pem -out csr.pem
3. 签发自签证书
openssl x509 -req -days 365 -in csr.pem -signkey private_key.pem -out certificate.crt
*/

1443 次点击
所在节点    Go 编程语言
5 条回复
tool2dx
92 天前
"当对已经签名过的 pdf 文件在末尾添加几个随机字节"

写点代码去掉就可以了。pdf 可以用软件遍历出文件结尾地址。后面如果是随机字节,并不会影响到签名本体,这部分内容本来就是多余的,又不会去读。
DefoliationM
92 天前
试试随机改几个字节,而不是添加到末尾?
cdx
92 天前
@DefoliationM
@tool2dx
直接对文档进行修改,检验的检测结果还是能通过
xxmaqzas
91 天前
不能把签过的 md5 存数据库么,直接校验文件的 md5 值
cdx
91 天前
@xxmaqzas 这样得到了文档的人没办法检测这个文档是否已经被篡改了,还需要借助系统

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1075355

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX