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

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

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

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

代码:
```go
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/ferify"
        "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)
        }
        ferifyPdf("c.pdf")
}


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

        resp, err := ferify.File(input_file)
        if err != nil {
                panic(err)
        }
        jsonData, err := json.MarshalIndent(resp, "", "\t")
        if err != nil {
                panic(err)
        }
        // 将 jsonData 的数据写入文件
        err = os.WriteFile("ferify.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
*/

```
举报· 55 次点击
登录 注册 站外分享
3 条回复  
tool2dx 初学 2024-9-24 12:43:47
"当对已经签名过的 pdf 文件在末尾添加几个随机字节"

写点代码去掉就可以了。pdf 可以用软件遍历出文件结尾地址。后面如果是随机字节,并不会影响到签名本体,这部分内容本来就是多余的,又不会去读。
DefoliationM 小成 2024-9-24 12:49:46
试试随机改几个字节,而不是添加到末尾?
xxmaqzas 初学 2024-9-24 17:29:48
不能把签过的 md5 存数据库么,直接校验文件的 md5 值
返回顶部