S/MIME 第 3 部分:本地安全电子邮件的即插即用
鸟
2019年12月1日
电子邮件
1 min read

关键要点
适合本地邮件传输代理(MTA)的S/MIME集成:了解如何在保留现有DKIM和合规设置的情况下,将签名和加密的电子邮件流注入PowerMTA、Momentum或SparkPost SMTP。
混合安全模型:结合S/MIME加密 + DKIM签名以确保在受监管的环境中,邮件的真实性和内容隐私。
部署流程:配置环境变量(SMTP_HOST,凭据,密钥),运行--sign --encrypt --send_smtp工作流程,并验证投递报告。
性能见解:测试显示SMTP和API注入的速度几乎相同(每条消息约60毫秒,较大文件为200–280毫秒)。
安全最佳实践:在受限文件(chmod 0700)中存储私钥和API密码,使用STARTTLS和认证过的SMTP会话。
使用案例:现代化企业的旧邮件系统可以在不放弃现有基础设施的情况下扩展端到端加密。
Q&A 精华
为什么为内置服务器适配 S/MIME 而不是云 API?
许多受监管行业(银行和医疗保健行业)必须在现场保留邮件。这种方法在添加现代加密保护的同时保持了对消息流的控制。
SMTP injection 如何与 PowerMTA 或 Momentum 配合工作?
您将完全形成的S/MIME消息注入到本地侦听器(端口25或私人VLAN)。这些MTA然后照常处理DKIM签名和交付。
S/MIME 是否与 DKIM 兼容?
是的 — DKIM在S/MIME加密后签署消息,因此身份验证和完整性检查保持不变。
如何保护我的SMTP凭证和密钥?
仅在封闭脚本中导出环境变量,并使用文件权限限制对自己的访问(
chmod 0700 my_envs.sh)。设置后我应该监控什么?
传输延迟(API vs SMTP)、TLS 握手成功率、DKIM/S-MIME 验证结果,以及“拒绝中继”或缺少身份验证的错误日志。
谁能最从这种配置中受益?
运行业务的组织需要合规级别的加密,但又希望不重写邮件管道的情况下使用即插即用的工具。
在这一部分,我们将探讨如何将该工具调整为将邮件流注入本地平台,例如PowerMTA和Momentum。
1. 入门指南
安装工具、获取密钥等与之前完全相同。当您使用类似PowerMTA或Momentum的本地电子邮件系统时,您已经负责设置发送域名、DKIM密钥等。运行本地系统的组织通常还需要解决电子邮件归档系统挑战以满足法规遵从性和数据保留要求。我们现在需要做的是提供一种方法,将完全形成的S/MIME消息注入到您的服务器中。
安装工具、获取密钥等与之前完全相同。当您使用类似PowerMTA或Momentum的本地电子邮件系统时,您已经负责设置发送域名、DKIM密钥等。运行本地系统的组织通常还需要解决电子邮件归档系统挑战以满足法规遵从性和数据保留要求。我们现在需要做的是提供一种方法,将完全形成的S/MIME消息注入到您的服务器中。
安装工具、获取密钥等与之前完全相同。当您使用类似PowerMTA或Momentum的本地电子邮件系统时,您已经负责设置发送域名、DKIM密钥等。运行本地系统的组织通常还需要解决电子邮件归档系统挑战以满足法规遵从性和数据保留要求。我们现在需要做的是提供一种方法,将完全形成的S/MIME消息注入到您的服务器中。
2. SMTP 注入向 Port25 PowerMTA
PowerMTA 支持多种消息注入方式,包括文件“拾取”目录、SMTP 和 API。在此使用的是 SMTP 方法。
为了展示最简单的设置,我们将在与 PowerMTA 相同的服务器上安装 S/MIME 工具。我们将消息注入监听器,该监听器默认在 TCP 端口 25 上打开,仅接受本地流量。
export SMTP_HOST=localhost
(如果您忘记了该步骤,当您尝试运行时,您将会看到:“环境变量 SMTP_HOST 未设置 – 停止”)
我们已经有了发件人的私钥 (steve@thetucks.com.pem) 和收件人的公钥 (steve.tuck@sparkpost.com.crt)。消息文件的前几行是:
To: SteveT <steve.tuck@sparkpost.com> From: Steve <steve@thetucks.com> Subject: This is a message created using HEML MIME-Version: 1.0 Content-Type: text/html; charset=utf-8 Content-Language: en-GB Content-Transfer-Encoding: 7bit
我们用以下命令发送消息:
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
我们看到:
Opened SMTP connection (plain) Host: localhost Port: 25 User: "" Password: "" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.028 seconds
该消息迅速到达了收件箱,并在 Mac 邮件中显示已签名和加密。

附加功能: PowerMTA 的 DKIM
配置 DKIM 非常简单,并且可以与 S/MIME 和谐共存。步骤如下:
使用 PowerMTA DKIM 向导 网站创建发送域的私钥(在我的情况下是 mypmta.thetucks.com.pem)和公共 DNS TXT 记录内容。
设置 DNS TXT 记录,并选择一个选择器。例如,我使用的选择器是 pmta201811。有效的选择器字符定义在这里。
将 mypmta.thetucks.com.pem 文件放到服务器的 /etc/pmta 目录中。
将以下内容添加到我的 /etc/pmta/config 并重新启动 pmta 服务。(这里,这些指令是写在全局范围内的;在生产系统中,您可能更愿意将它们添加在虚拟-mta 下。)
host-name thetucks.com domain-key pmta201811,*,/etc/pmta/mypmta.thetucks.com.pem <domain *> dkim-sign yes </domain>
通过 MX Toolbox 检查 DNS 记录显示正常,DKIM 现已激活。

PowerMTA 支持多种消息注入方式,包括文件“拾取”目录、SMTP 和 API。在此使用的是 SMTP 方法。
为了展示最简单的设置,我们将在与 PowerMTA 相同的服务器上安装 S/MIME 工具。我们将消息注入监听器,该监听器默认在 TCP 端口 25 上打开,仅接受本地流量。
export SMTP_HOST=localhost
(如果您忘记了该步骤,当您尝试运行时,您将会看到:“环境变量 SMTP_HOST 未设置 – 停止”)
我们已经有了发件人的私钥 (steve@thetucks.com.pem) 和收件人的公钥 (steve.tuck@sparkpost.com.crt)。消息文件的前几行是:
To: SteveT <steve.tuck@sparkpost.com> From: Steve <steve@thetucks.com> Subject: This is a message created using HEML MIME-Version: 1.0 Content-Type: text/html; charset=utf-8 Content-Language: en-GB Content-Transfer-Encoding: 7bit
我们用以下命令发送消息:
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
我们看到:
Opened SMTP connection (plain) Host: localhost Port: 25 User: "" Password: "" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.028 seconds
该消息迅速到达了收件箱,并在 Mac 邮件中显示已签名和加密。

附加功能: PowerMTA 的 DKIM
配置 DKIM 非常简单,并且可以与 S/MIME 和谐共存。步骤如下:
使用 PowerMTA DKIM 向导 网站创建发送域的私钥(在我的情况下是 mypmta.thetucks.com.pem)和公共 DNS TXT 记录内容。
设置 DNS TXT 记录,并选择一个选择器。例如,我使用的选择器是 pmta201811。有效的选择器字符定义在这里。
将 mypmta.thetucks.com.pem 文件放到服务器的 /etc/pmta 目录中。
将以下内容添加到我的 /etc/pmta/config 并重新启动 pmta 服务。(这里,这些指令是写在全局范围内的;在生产系统中,您可能更愿意将它们添加在虚拟-mta 下。)
host-name thetucks.com domain-key pmta201811,*,/etc/pmta/mypmta.thetucks.com.pem <domain *> dkim-sign yes </domain>
通过 MX Toolbox 检查 DNS 记录显示正常,DKIM 现已激活。

PowerMTA 支持多种消息注入方式,包括文件“拾取”目录、SMTP 和 API。在此使用的是 SMTP 方法。
为了展示最简单的设置,我们将在与 PowerMTA 相同的服务器上安装 S/MIME 工具。我们将消息注入监听器,该监听器默认在 TCP 端口 25 上打开,仅接受本地流量。
export SMTP_HOST=localhost
(如果您忘记了该步骤,当您尝试运行时,您将会看到:“环境变量 SMTP_HOST 未设置 – 停止”)
我们已经有了发件人的私钥 (steve@thetucks.com.pem) 和收件人的公钥 (steve.tuck@sparkpost.com.crt)。消息文件的前几行是:
To: SteveT <steve.tuck@sparkpost.com> From: Steve <steve@thetucks.com> Subject: This is a message created using HEML MIME-Version: 1.0 Content-Type: text/html; charset=utf-8 Content-Language: en-GB Content-Transfer-Encoding: 7bit
我们用以下命令发送消息:
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
我们看到:
Opened SMTP connection (plain) Host: localhost Port: 25 User: "" Password: "" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.028 seconds
该消息迅速到达了收件箱,并在 Mac 邮件中显示已签名和加密。

附加功能: PowerMTA 的 DKIM
配置 DKIM 非常简单,并且可以与 S/MIME 和谐共存。步骤如下:
使用 PowerMTA DKIM 向导 网站创建发送域的私钥(在我的情况下是 mypmta.thetucks.com.pem)和公共 DNS TXT 记录内容。
设置 DNS TXT 记录,并选择一个选择器。例如,我使用的选择器是 pmta201811。有效的选择器字符定义在这里。
将 mypmta.thetucks.com.pem 文件放到服务器的 /etc/pmta 目录中。
将以下内容添加到我的 /etc/pmta/config 并重新启动 pmta 服务。(这里,这些指令是写在全局范围内的;在生产系统中,您可能更愿意将它们添加在虚拟-mta 下。)
host-name thetucks.com domain-key pmta201811,*,/etc/pmta/mypmta.thetucks.com.pem <domain *> dkim-sign yes </domain>
通过 MX Toolbox 检查 DNS 记录显示正常,DKIM 现已激活。

3. SMTP 注入倾向于势头
Momentum 支持多种消息注入方式,包括API和SMTP。SMTP 是这里使用的方法,运行在已经运行Momentum的主机上。我们将保持其配置不变,因为它已经具备接受来自其他批准主机的入站注入的能力。
这是生产设置的缩小版本,其中“generation”节点和MTA节点是分开的,但通过私有VLAN和负载均衡器紧密耦合,传输内部SMTP注入流量。

S/MIME工具与之前一样安装,我们将向SMTP主机(MTA)的地址注入消息:
export SMTP_HOST=xx.xx.xx.xx # 在此设置您自己的MTA/VIP地址
和之前一样,我们在“generation”节点上已经拥有发件人的私钥(steve@thetucks.com.pem)和收件人的公钥(steve.tuck@sparkpost.com.crt)。消息文件的前几行匹配这些地址。
我们使用与之前完全相同的命令从“generation”节点发送消息,消息显示在inbox中。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
正如您所料,S/MIME也可以愉快地与Momentum的DKIM signing共存。
Momentum 支持多种消息注入方式,包括API和SMTP。SMTP 是这里使用的方法,运行在已经运行Momentum的主机上。我们将保持其配置不变,因为它已经具备接受来自其他批准主机的入站注入的能力。
这是生产设置的缩小版本,其中“generation”节点和MTA节点是分开的,但通过私有VLAN和负载均衡器紧密耦合,传输内部SMTP注入流量。

S/MIME工具与之前一样安装,我们将向SMTP主机(MTA)的地址注入消息:
export SMTP_HOST=xx.xx.xx.xx # 在此设置您自己的MTA/VIP地址
和之前一样,我们在“generation”节点上已经拥有发件人的私钥(steve@thetucks.com.pem)和收件人的公钥(steve.tuck@sparkpost.com.crt)。消息文件的前几行匹配这些地址。
我们使用与之前完全相同的命令从“generation”节点发送消息,消息显示在inbox中。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
正如您所料,S/MIME也可以愉快地与Momentum的DKIM signing共存。
Momentum 支持多种消息注入方式,包括API和SMTP。SMTP 是这里使用的方法,运行在已经运行Momentum的主机上。我们将保持其配置不变,因为它已经具备接受来自其他批准主机的入站注入的能力。
这是生产设置的缩小版本,其中“generation”节点和MTA节点是分开的,但通过私有VLAN和负载均衡器紧密耦合,传输内部SMTP注入流量。

S/MIME工具与之前一样安装,我们将向SMTP主机(MTA)的地址注入消息:
export SMTP_HOST=xx.xx.xx.xx # 在此设置您自己的MTA/VIP地址
和之前一样,我们在“generation”节点上已经拥有发件人的私钥(steve@thetucks.com.pem)和收件人的公钥(steve.tuck@sparkpost.com.crt)。消息文件的前几行匹配这些地址。
我们使用与之前完全相同的命令从“generation”节点发送消息,消息显示在inbox中。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
正如您所料,S/MIME也可以愉快地与Momentum的DKIM signing共存。
4. SMTP injection towards SparkPost
在第2部分中,我们使用SparkPost传输REST API来注入消息。当然,也可以使用SMTP将消息注入到SparkPost。我们像这样设置环境变量:
export SMTP_PASSWORD=<<YOUR_API_KEY_HERE>
如果您正在使用SparkPost EU-hosted service,那么将SMTP_HOST设置为smtp.eu.sparkpostmail.com。
(在此查看更多选项——例如,您可以选择端口2525而不是587进行注入。)
下面的输出显示使用了STARTTLS,以及用户名和密码。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
您将看到:
Opened SMTP connection (STARTTLS) Host: smtp.sparkpostmail.com Port: 587 User: "SMTP_Injection" Password: "****************************************" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.057 seconds
密码使用替代***字符打印,因此如果有人偷看您的屏幕,您不会泄露密钥的隐私。
保护您的凭据
注意,环境变量可以在Shell脚本文件或类似文件中设置,以免重新键入。如果这样做,请通过限制对该文件的访问来保护您的密码/API 密钥。例如,如果您的凭据设置文件名为my_envs.sh,请运行:
chmod 0700 my_envs.sh
您可能会看到的SMTP 相关警告
SparkPost的SMTP注入非常严格,这正如您所预料的公共服务。如果您没有设置SMTP端口号,您将看到一个警告:
{'bob.lumreeker@gmail.com': (550, b'5.7.1 relaying denied')}
如果您没有设置SMTP用户名或没有设置密码,您将看到:
(530, b'5.7.1 Authorization required. Ref. https://developers.sparkpost.com/api/index#header-smtp-relay-endpoints', 'steve@thetucks.com')
这些错误消息只是从Python SMTP库中直接报告,因此格式保持不变。
在第2部分中,我们使用SparkPost传输REST API来注入消息。当然,也可以使用SMTP将消息注入到SparkPost。我们像这样设置环境变量:
export SMTP_PASSWORD=<<YOUR_API_KEY_HERE>
如果您正在使用SparkPost EU-hosted service,那么将SMTP_HOST设置为smtp.eu.sparkpostmail.com。
(在此查看更多选项——例如,您可以选择端口2525而不是587进行注入。)
下面的输出显示使用了STARTTLS,以及用户名和密码。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
您将看到:
Opened SMTP connection (STARTTLS) Host: smtp.sparkpostmail.com Port: 587 User: "SMTP_Injection" Password: "****************************************" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.057 seconds
密码使用替代***字符打印,因此如果有人偷看您的屏幕,您不会泄露密钥的隐私。
保护您的凭据
注意,环境变量可以在Shell脚本文件或类似文件中设置,以免重新键入。如果这样做,请通过限制对该文件的访问来保护您的密码/API 密钥。例如,如果您的凭据设置文件名为my_envs.sh,请运行:
chmod 0700 my_envs.sh
您可能会看到的SMTP 相关警告
SparkPost的SMTP注入非常严格,这正如您所预料的公共服务。如果您没有设置SMTP端口号,您将看到一个警告:
{'bob.lumreeker@gmail.com': (550, b'5.7.1 relaying denied')}
如果您没有设置SMTP用户名或没有设置密码,您将看到:
(530, b'5.7.1 Authorization required. Ref. https://developers.sparkpost.com/api/index#header-smtp-relay-endpoints', 'steve@thetucks.com')
这些错误消息只是从Python SMTP库中直接报告,因此格式保持不变。
在第2部分中,我们使用SparkPost传输REST API来注入消息。当然,也可以使用SMTP将消息注入到SparkPost。我们像这样设置环境变量:
export SMTP_PASSWORD=<<YOUR_API_KEY_HERE>
如果您正在使用SparkPost EU-hosted service,那么将SMTP_HOST设置为smtp.eu.sparkpostmail.com。
(在此查看更多选项——例如,您可以选择端口2525而不是587进行注入。)
下面的输出显示使用了STARTTLS,以及用户名和密码。
./sparkpostSMIME.py tests/fancy-HTML-to-smt.eml --sign --encrypt --send_smtp
您将看到:
Opened SMTP connection (STARTTLS) Host: smtp.sparkpostmail.com Port: 587 User: "SMTP_Injection" Password: "****************************************" Sending: tests/fancy-HTML-to-smt.eml From: Steve <steve@thetucks.com> To: SteveT <steve.tuck@sparkpost.com> OK - in 0.057 seconds
密码使用替代***字符打印,因此如果有人偷看您的屏幕,您不会泄露密钥的隐私。
保护您的凭据
注意,环境变量可以在Shell脚本文件或类似文件中设置,以免重新键入。如果这样做,请通过限制对该文件的访问来保护您的密码/API 密钥。例如,如果您的凭据设置文件名为my_envs.sh,请运行:
chmod 0700 my_envs.sh
您可能会看到的SMTP 相关警告
SparkPost的SMTP注入非常严格,这正如您所预料的公共服务。如果您没有设置SMTP端口号,您将看到一个警告:
{'bob.lumreeker@gmail.com': (550, b'5.7.1 relaying denied')}
如果您没有设置SMTP用户名或没有设置密码,您将看到:
(530, b'5.7.1 Authorization required. Ref. https://developers.sparkpost.com/api/index#header-smtp-relay-endpoints', 'steve@thetucks.com')
这些错误消息只是从Python SMTP库中直接报告,因此格式保持不变。
哪一个更快 – SMTP 或 API?
坦白说,S/MIME 不太可能成为高频使用案例,但拥有相同工具的两个输出选项实在是让我们想进行一场比赛!
这里使用的“Avocado”邮箱测试文件大约为19KB。通过bash循环重复测试10次,显示SMTP和API的平均时间相似,大约每条消息为60毫秒,这相当快。在这种情况下,我们从与SparkPost.com同一托管区域内的中型EC2实例注入,这是一种保持网络往返时间较低的好方法。
使用更大的测试文件(577KB)重复此测试,API大约用了200毫秒,而SMTP每条消息用了280毫秒——对于一个大小大30倍的文件来说仍然令人印象深刻。当然,结果可能因位置、网络拥堵等因素而异,但性能不太可能成为问题。
如果您真的需要最大性能,一个好的起点是根据我们的传输最佳实践建议启动一组并发注入进程/会话——例如,从一个supervisor任务中。
坦白说,S/MIME 不太可能成为高频使用案例,但拥有相同工具的两个输出选项实在是让我们想进行一场比赛!
这里使用的“Avocado”邮箱测试文件大约为19KB。通过bash循环重复测试10次,显示SMTP和API的平均时间相似,大约每条消息为60毫秒,这相当快。在这种情况下,我们从与SparkPost.com同一托管区域内的中型EC2实例注入,这是一种保持网络往返时间较低的好方法。
使用更大的测试文件(577KB)重复此测试,API大约用了200毫秒,而SMTP每条消息用了280毫秒——对于一个大小大30倍的文件来说仍然令人印象深刻。当然,结果可能因位置、网络拥堵等因素而异,但性能不太可能成为问题。
如果您真的需要最大性能,一个好的起点是根据我们的传输最佳实践建议启动一组并发注入进程/会话——例如,从一个supervisor任务中。
坦白说,S/MIME 不太可能成为高频使用案例,但拥有相同工具的两个输出选项实在是让我们想进行一场比赛!
这里使用的“Avocado”邮箱测试文件大约为19KB。通过bash循环重复测试10次,显示SMTP和API的平均时间相似,大约每条消息为60毫秒,这相当快。在这种情况下,我们从与SparkPost.com同一托管区域内的中型EC2实例注入,这是一种保持网络往返时间较低的好方法。
使用更大的测试文件(577KB)重复此测试,API大约用了200毫秒,而SMTP每条消息用了280毫秒——对于一个大小大30倍的文件来说仍然令人印象深刻。当然,结果可能因位置、网络拥堵等因素而异,但性能不太可能成为问题。
如果您真的需要最大性能,一个好的起点是根据我们的传输最佳实践建议启动一组并发注入进程/会话——例如,从一个supervisor任务中。



