如何用Python的cryptography模块实现SMIME签名邮件及主题显示问题
SMIME签名后邮件主题无法识别的解决方法
问题出在你直接发送了cryptography生成的SMIME签名字节流,而分离式SMIME签名需要将原始邮件内容与签名数据封装为multipart/signed格式的邮件,这样邮件客户端才能正确解析邮件头(包括主题)并验证签名。
以下是两个版本的修正代码:
基于MIMEMultipart的修正版本
import smtplib import base64 from email.header import Header from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.utils import formataddr, formatdate from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.serialization import pkcs7 from jinja2 import Template email_from = "root@wikipedia.org" email_to = "user@wikipedia.org" EMAIL_TEMPLATES_DIR = "./templates" template = "mail_template" environment = {} # 替换为你的渲染参数 CA_CERT_PATH = "./ca.crt" CA_KEY_PATH = "./ca.key" # 1. 构建原始邮件内容(未签名) message = MIMEMultipart("mixed") message["Subject"] = Header("mail subject", "utf-8").encode() message["From"] = formataddr(("Sender", email_from)) message["To"] = email_to message["Date"] = formatdate(localtime=True) message["Auto-Submitted"] = "auto-generated" html_template = (Path(EMAIL_TEMPLATES_DIR) / f"{template}.html").read_text() html_j2_template = Template(html_template) html = html_j2_template.render(environment) message.attach(MIMEText(html, "html")) # 2. 生成分离式SMIME签名 original_content = message.as_bytes() ca_cert = open(CA_CERT_PATH, "rb").read() ca_key = open(CA_KEY_PATH, "rb").read() cert = x509.load_pem_x509_certificate(ca_cert) key = serialization.load_pem_private_key(ca_key, None) options = [pkcs7.PKCS7Options.DetachedSignature] # 使用DER编码签名,后续转base64 signed_data = ( pkcs7.PKCS7SignatureBuilder() .set_data(original_content) .add_signer(cert, key, hashes.SHA256()) .sign(serialization.Encoding.DER, options) ) # 3. 构建multipart/signed格式的最终邮件 signed_message = MIMEMultipart("signed", protocol="application/x-pkcs7-signature", micalg="sha-256") # 复制原始邮件的头部信息 signed_message["Subject"] = message["Subject"] signed_message["From"] = message["From"] signed_message["To"] = message["To"] signed_message["Date"] = message["Date"] signed_message["Auto-Submitted"] = message["Auto-Submitted"] # 添加原始邮件内容作为第一部分 signed_message.attach(MIMEText(original_content, "rfc822")) # 添加签名作为附件(application/pkcs7-signature类型) signature_part = MIMEBase("application", "pkcs7-signature", name="smime.p7s") signature_part.set_payload(base64.b64encode(signed_data).decode()) signature_part.add_header("Content-Transfer-Encoding", "base64") signature_part.add_header("Content-Disposition", "attachment; filename=\"smime.p7s\"") signed_message.attach(signature_part) # 4. 发送邮件 with smtplib.SMTP() as server: server.connect(25) server.sendmail(email_from, email_to, signed_message.as_bytes())
适用于Python>=3.6的EmailMessage修正版本
import smtplib import base64 from email.headerregistry import Address from email.message import EmailMessage from email.utils import formatdate from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.serialization import pkcs7 from jinja2 import Template email_from = "root@wikipedia.org" email_to = "user@wikipedia.org" EMAIL_TEMPLATES_DIR = "./templates" template = "mail_template" environment = {} # 替换为你的渲染参数 subject_template = "mail subject" CA_CERT_PATH = "./ca.crt" CA_KEY_PATH = "./ca.key" # 1. 构建原始邮件内容(未签名) message = EmailMessage() message["Subject"] = subject_template message["From"] = Address("Sender", email_from) message["To"] = Address("Receiver", email_to) message["Date"] = formatdate(localtime=True) message["Auto-Submitted"] = "auto-generated" html_template = (Path(EMAIL_TEMPLATES_DIR) / f"{template}.html").read_text() html_j2_template = Template(html_template) html = html_j2_template.render(environment) message.set_content(html, subtype="html") # 2. 生成分离式SMIME签名 original_content = message.as_bytes() ca_cert = open(CA_CERT_PATH, "rb").read() ca_key = open(CA_KEY_PATH, "rb").read() cert = x509.load_pem_x509_certificate(ca_cert) key = serialization.load_pem_private_key(ca_key, None) options = [pkcs7.PKCS7Options.DetachedSignature] # 使用DER编码签名,后续转base64 signed_data = ( pkcs7.PKCS7SignatureBuilder() .set_data(original_content) .add_signer(cert, key, hashes.SHA256()) .sign(serialization.Encoding.DER, options) ) # 3. 构建multipart/signed格式的最终邮件 signed_message = EmailMessage() signed_message.make_mixed() # 设置multipart类型和SMIME相关头 signed_message.set_type("multipart/signed") signed_message.add_header("Protocol", "application/x-pkcs7-signature") signed_message.add_header("MICAlg", "sha-256") # 复制原始邮件的头部信息 signed_message["Subject"] = message["Subject"] signed_message["From"] = message["From"] signed_message["To"] = message["To"] signed_message["Date"] = message["Date"] signed_message["Auto-Submitted"] = message["Auto-Submitted"] # 添加原始邮件内容作为第一部分 signed_message.add_attachment( original_content, maintype="message", subtype="rfc822" ) # 添加签名作为附件 signed_message.add_attachment( base64.b64encode(signed_data).decode(), maintype="application", subtype="pkcs7-signature", filename="smime.p7s", headers={"Content-Transfer-Encoding": "base64"} ) # 4. 发送邮件 with smtplib.SMTP() as server: server.connect(25) server.sendmail(email_from, email_to, signed_message.as_bytes())
关键修正点
- 不再直接发送签名后的字节流,而是将原始邮件与签名封装为
multipart/signed格式 - 签名使用DER编码后转base64,符合邮件传输的编码要求
- 保留原始邮件的所有头部信息,确保邮件客户端能正确识别主题等字段
内容的提问来源于stack exchange,提问作者ikreb
相关产品推荐
相关产品推荐

