You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 09:01:12