如何使用Python定位PDF文件中的数字签名并提取相关信息
问题:使用Python检测PDF数字签名并提取签署人邮箱信息
- 需求为用Python批量处理多份PDF文件,检测文件是否存在数字签名,签名存在时提取其中的详情信息,本次需要提取的核心字段是签署人邮箱,无邮箱的签名属于例外场景。
- 最初的实现思路是先把文档内所有签名对象存入列表,再遍历列表读取每个签名的详情,需要确认该方案是否可行,以及签名对象的具体定位方法。
- 目前网上可检索到的资料大多围绕PDF签名验证、PDF签署功能展开,和当前提取签名属性的需求不匹配。
问题更新
- 目前已经可以定位到PDF中的签名对象并获取内容,但在解码环节遇到问题。
- 早期临时定位方案是直接指定签名对象的索引,但不同PDF的签名对象索引不固定,后续优化思路是:签名对象的
Type属性固定为Sig,可以遍历所有对象检查Type属性,属性值为Sig即为目标签名对象。 - 获取到的签名内容类型为
pdfreader.types.native.HexString,原计划直接解码该字符串获取数据,但转字节后用utf-8、ascii解码均报错:- UTF-8解码报错:
'utf-8' codec can't decode byte 0x82 in position 1: invalid start byte - ASCII解码报错:
'ascii' codec can't decode byte 0x82 in position 1: ordinal not in range(128)
需要可行的解码/字段提取方案。
- UTF-8解码报错:
解决方案
- 之前解码报错的核心原因是:签名的
content字段本身不存储需要的业务属性,之前控制台输出的签名对象内容不完整(大概率是Visual Studio调试控制台的输出截断问题),数字签名对象实际包含更多直接可读取的属性,本次需要的邮箱信息就直接存储在签名对象的Name属性下,不需要解码HexString内容。 - 剩下需要解决的是获取PDF文件内的总对象数问题:文档最后一个trailer块中包含
Size属性,对应文件内的对象总数。由于pdfreader的PDFTrailer对象没有直接暴露该属性,本次通过字符串操作提取该值。
注意:遍历对象的循环中使用了try-catch兜底逻辑,更严谨的实现应该在读取对象前先判断对象是否存在、是否包含
Type属性,不建议直接照搬以下代码,建议根据实际场景补充更严谨的判断逻辑。
完整实现代码如下:
from pdfreader import PDFDocument filename = r"<filepath.pdf>" fd = open(filename, "rb") doc = PDFDocument(fd) pdf_content = str(fd.read()) nr_of_obj = 0 nr_of_signatures = 0 signature_names = [] # 该场景下存储的是签署人邮箱 s_trailer = 'trailer' s_startxref = 'startxref' # 定位文档最后一个trailer块的起止位置,末尾偏移2是为了跳过trailer前后的换行符 last_trailer_start = pdf_content.rfind(s_trailer)+len(s_trailer)+2 last_trailer_end = pdf_content.rfind(s_startxref)-2 # 拼接提取trailer块内容 trailer = "" for i in range(last_trailer_start, last_trailer_end): trailer += pdf_content[i] # 拆分trailer属性,定位并读取Size字段值(即PDF总对象数) trailer = trailer.split('/') for attribute in trailer: if attribute.find('Size') >= 0: nr_of_obj = int(attribute.replace('Size','').strip()) break # 遍历所有对象,筛选Type为Sig的签名对象,提取Name字段 # 注意:不是所有编号对应的对象都存在,也不是所有对象都有Type属性,因此加try-catch兜底 for i in range(1, nr_of_obj): try: raw_obj = doc.locate_object(i,0) obj = doc.build(raw_obj) if obj.Type == 'Sig': nr_of_signatures +=1 signature_names.append(obj.Name) except: continue print(nr_of_signatures) print(signature_names)
内容的提问来源于stack exchange,提问作者Oskars
相关产品推荐
相关产品推荐

