Python EmailMessage创建PGP签名邮件时多余MIME头问题
Python新版email API构造PGP签名邮件问题解决
问题背景
重构基于旧版Python email API实现的PGP分离签名多部分邮件脚本时,使用新版API编写的代码出现签名MIME段被额外添加冗余头部的问题。
旧版可正常运行代码
from email.message import Message from email.mime.multipart import MIMEMultipart import gnupg def messageFromSignature(signature): message = Message() message['Content-Type'] = 'application/pgp-signature; name="signature.asc"' message['Content-Description'] = 'OpenPGP digital signature' message.set_payload(signature) return message def send_message(subject, body): basemsg = Message() basemsg.set_payload(body) basemsg.set_charset('utf-8') gpg = gnupg.GPG() basetext = basemsg.as_string().replace('\n', '\r\n') signature = str(gpg.sign(basetext, keyid='xxxxxxxxxxxxxxxx', detach=True)) if signature: signmsg = messageFromSignature(signature) msg = MIMEMultipart(_subtype="signed", micalg="pgp-sha512", protocol="application/pgp-signature") msg.attach(basemsg) msg.attach(signmsg) else: print('Warning: failed to sign the message!')
新版出错代码
from email.message import EmailMessage, MIMEPart import gnupg def messageFromSignature(signature): message = MIMEPart() message.set_content(signature) message.replace_header('Content-Type', 'application/pgp-signature; name="signature.asc"') message['Content-Description'] = 'OpenPGP digital signature' return message def send_message(subject, body): basemsg = EmailMessage() basemsg.set_content(body) basemsg.set_charset('utf-8') gpg = gnupg.GPG() basetext = basemsg.as_string().replace('\n', '\r\n') signature = str(gpg.sign(basetext, keyid='xxxxxxxxxxxxxxxx', detach=True)) if signature: signmsg = messageFromSignature(signature) msg = basemsg msg.make_mixed() msg.add_attachment(signmsg) msg.replace_header('Content-Type', 'multipart/signed; micalg="pgp-sha512"; protocol="application/pgp-signature";') else: print('Warning: failed to sign the message!')
错误现象
签名段被额外封装了一层message/rfc822头部,自动添加了MIME-Version、Content-Disposition等冗余字段,实际生成的MIME段如下:
--===============0054553979676888393== Content-Type: message/rfc822 Content-Transfer-Encoding: 8bit MIME-Version: 1.0 Content-Disposition: attachment Content-Type: application/pgp-signature; name="signature.asc" Content-Transfer-Encoding: 7bit Content-Description: OpenPGP digital signature -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQQTeptvPezKETGQAId81GVnkrOh+QUCYqWYUAAKCRB81GVnkrOh +SJSAQCre0cbhMf6frI9jILwc0XXGkByXUgiBkexcpYnXN/HzAD/WaPZA5hTutMg 5lPOQdZGaeVpsApYDL/fmbH6SniTvAg= =xuOv -----END PGP SIGNATURE----- --===============0054553979676888393==--
问题解答
1. 如何实现旧版attach方法的效果,无冗余头部添加签名子段
核心问题是用错了API方法:
add_attachment的设计用途是添加文件类附件,传入参数预期是字符串、字节流或者文件对象。当传入MIMEPart/Message实例时,方法会默认把传入对象当成独立的完整嵌套邮件,自动封装message/rfc822外层头、添加附件处置标记,才会出现冗余头部。- 新版API完全保留了
attach()方法,行为和旧版完全一致:直接将传入的MIME部件作为同级子段追加到当前多部分消息的payload列表中,不会做任何额外封装。
正确实现步骤:
- 构造签名段时,直接在
set_content中指定内容类型,不需要先设置默认文本类型再替换头,避免生成多余的传输编码配置错误。 - 不要在正文消息对象上直接调用
make_mixed()修改类型,单独构造顶层multipart/signed类型消息,再依次attach正文段和签名段即可。
修正后的核心代码:
from email.message import EmailMessage, MIMEPart import gnupg def messageFromSignature(signature): message = MIMEPart() # 直接指定签名段的内容类型、传输编码,避免后续替换头 message.set_content( signature, cte='7bit', content_type='application/pgp-signature', params={'name': 'signature.asc'} ) message['Content-Description'] = 'OpenPGP digital signature' return message def send_message(subject, body): # 构造正文段 basemsg = EmailMessage() basemsg.set_content(body, charset='utf-8') gpg = gnupg.GPG() basetext = basemsg.as_string().replace('\n', '\r\n') signature = str(gpg.sign(basetext, keyid='xxxxxxxxxxxxxxxx', detach=True)) if signature: signmsg = messageFromSignature(signature) # 构造顶层signed类型多部分消息 msg = EmailMessage() msg['Subject'] = subject # 手动设置multipart/signed的Content-Type参数 msg['Content-Type'] = 'multipart/signed; micalg="pgp-sha512"; protocol="application/pgp-signature"' # 用attach方法追加子段,和旧版行为完全一致,无额外封装 msg.attach(basemsg) msg.attach(signmsg) else: print('Warning: failed to sign the message!')
2. 为什么add_attachment会为MIMEPart添加MIME-Version头部
这是add_attachment方法的预设逻辑,不是bug:
当方法检测到传入的是Message/MIMEPart类实例时,会将其判定为独立的、可单独解析的完整RFC822格式消息。按照MIME规范,独立存在的MIME消息必须携带MIME-Version: 1.0头部,因此方法会自动补全该头部,同时添加对应的嵌套消息封装头。
这个行为和“MIME子部件不需要自带MIME-Version”的规则不冲突——因为add_attachment本来就不是用来添加同级MIME子部件的方法,它处理传入的MIMEPart时,是把它当成一个独立的嵌套消息附件,而非当前多部分消息的直接子段。
内容的提问来源于stack exchange,提问作者janeden
相关产品推荐
相关产品推荐

