跳转到内容
View in the app

A better way to browse. Learn more.

彼岸论坛

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
欢迎抵达彼岸 彼岸花开 此处谁在 -彼岸论坛

[Go 编程语言] 求助:怎么使用 go 实现 pdf 的数字签名与校验

发表于

目前有业务需要给 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
*/

Featured Replies

No posts to show

创建帐户或登录来提出意见

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.