本文档内容仅用于PDF文件格式结构及数字签名加密算法的底层技术研究与学习。
文中涉及的操作流程,严格限定于用户拥有完全著作权的原创文档,或已进入公有领域(Public Domain) 的无版权文件。
用户若将该技术应用于任何受版权保护、未经授权的商业文档、合同或出版物,由此产生的一切民事纠纷及刑事责任,均由实际操作者自行承担,与本文作者及发布平台无关。
请用户在阅读完毕后24小时内删除相关测试数据
deepseek-v4-pro编写
这大肥鱼还是有俩下的,搜索引擎搜索一堆收钱网站还有垃圾答案,真的无语了
1数字签名不是独立文件,是挂在 PDF 对象引用链上的一串对象:
/AcroForm /Fields
└─ 签名字段(/FT /Sig)
└─ /V 签名值对象
└─ /Contents ← PKCS#7 签名数据真正在这
└─ /ByteRange ← 签名覆盖的字节区间
2.去签名就是“断开引用”:删掉页面 /Annots 里的签名挂件
再删掉 /AcroForm /Fields 里的签名字段
删完之后,/V → /Contents 这一串签名对象就没人引用了,变成孤立对象
3.最后一步最关键:pdf.save() 不是在原文件上打补丁,让 pikepdf(底层是 qpdf)把整个 PDF 重新构建一遍
重新生成对象和交叉引用表
孤立对象不会被写进去,于是那几千字节的 PKCS#7 签名数据就物理消失了
文件会缩小几十 KB,缩掉的基本就是签名数据
外部依赖
pip install pikepdf
核心算法
Python
import pikepdf
SIG = pikepdf.Name("/Sig")
# Enfocus PitStop / QITE 等预检工具留下的非标准扩展数据
JUNK_KEYS = ("/FICL:Enfocus", "/QITE_DocInfo", "/CP2")
def has_signature(pdf):
"""判断文档里是否有签名字段。"""
acro = pdf.Root.get("/AcroForm")
if acro is None:
return False
fields = acro.get("/Fields")
if fields is None:
return False
return any(f.get("/FT") == SIG for f in fields)
def strip_signatures(pdf):
"""移除签名挂件、签名字段,以及预检残留数据。"""
root = pdf.Root
# 1) 删除页面上的签名挂件(widget annotation,/FT = /Sig)
for page in pdf.pages:
if "/Annots" not in page:
continue
kept = pikepdf.Array()
for annot in page.Annots:
if annot.get("/FT") != SIG:
kept.append(annot)
page.Annots = kept
# 2) 删除 AcroForm 里的签名字段;表单空了就整个移除 AcroForm
if "/AcroForm" in root:
acro = root.AcroForm
if "/Fields" in acro:
kept = pikepdf.Array()
for field in acro.Fields:
if field.get("/FT") != SIG:
kept.append(field)
if len(kept) == 0:
del root["/AcroForm"]
else:
acro.Fields = kept
if "/SigFlags" in acro:
del acro["/SigFlags"]
# 3) 清理预检工具残留
for key in JUNK_KEYS:
if key in root:
del root[key]
def remove_signature(src, dst):
pdf = pikepdf.open(src)
try:
if not has_signature(pdf):
return "skip"
strip_signatures(pdf)
pdf.save(dst) # 关键:重写整个 PDF
finally:
pdf.close()
return "done"
