邮件的签名与加密应该按什么顺序组合,为什么常出现兼容性故障?
RFC 8551 明确:签名与加密可以任意嵌套,因为一个安全服务的输出本身就是合法的 MIME 实体,可以作为另一个安全服务的输入。规范并不强制唯一顺序,但不同顺序的语义差别很大:
- 先签名后加密(sign-then-encrypt):签名位于加密层内部。解密后才能看到签名,因此签名只对能解密的收件人可见,第三方即使截获也无法确认发送者。这是多数场景的默认选择,语义上表达"我对这段内容负责,并且只想让你看到"。
- 先加密后签名(encrypt-then-sign):签名覆盖的是密文。任何人都能验证"某人发送了这段密文",但签名者未必知道密文内容——这一点在安全分析上很关键,它无法证明签名者认可明文内容,容易被用于剥离并重新包装的攻击。
- 三重包装(triple wrapping,见 RFC 2634):内层签名 → 加密 → 外层签名。内层签名绑定原始内容与作者,外层签名可由网关等中间实体施加,用于在不解密的前提下承载和保护安全标签、传输过程属性。
选型建议很直接:如无特殊需求,采用先签名后加密;若需要中间设备在不接触明文的情况下附加可验证元数据,才引入三重包装。
这是最容易被误解的一点。S/MIME 与 PGP/MIME 保护的是MIME 实体(正文与附件),而 Subject、From、To、Date 等 RFC 5322 信头位于加密与签名边界之外。其直接后果是:
- 加密邮件的主题行仍然以明文在网络上传输并留存于各级日志;
- 签名不覆盖 From,攻击者转发一封合法签名的加密邮件并伪造 From 时,签名本身依然有效;
- 用户界面上"锁形图标 + 已验证签名"的提示,可能与实际被保护的范围不一致,形成安全感错觉。
缓解手段是把关键信头一并封装进被保护的 MIME 结构(业界称为 header protection / memory hole,IETF LAMPS 工作组正在推进相应标准化),并在客户端侧只展示受保护副本。在标准落地并被广泛实现之前,务实的做法是:不要把敏感信息写进主题行,并在安全培训中明确告知用户主题不加密。
邮件在投递路径上会被多个环节改写:MSA 按 RFC 6409 补 Message-ID / Date、网关插入免责声明或扫描标记、邮件列表在主题前加标签并追加页脚、反垃圾系统重写附件名或做编码转换。这些操作对普通邮件无害,但会无差别地破坏已有的 S/MIME 或 PGP 签名。
清晰签名(multipart/signed)尤其脆弱,因为被签名的内容以可见 MIME 部分的形式存在,任何对换行、传输编码、边界字符串的规范化都会改变摘要输入。不透明签名(pkcs7-mime signed-data)由于整体是一团二进制,反而不易被"好心"改写,但代价是不支持的客户端完全读不到内容。
排障顺序建议:先确认签名是在客户端生成还是网关代签;再在发件方出口与收件方入口分别取原始报文做逐字节比对,定位是哪一跳改写了内容;最后针对该环节配置例外——例如对带 multipart/signed 或 application/pkcs7-mime 的邮件跳过免责声明注入与内容重写。
DKIM(RFC 6376)与 S/MIME、PGP 处在不同层次、服务于不同目的,混淆两者是策略设计中的常见错误:
- DKIM 是域级签名,由发送域的基础设施施加,证明"这封信确实由该域发出且未被改写",供接收方系统做策略判定,用户通常不感知。
- S/MIME / PGP 是用户级签名,证明"这段内容由该自然人或该证书持有者认可",具备面向人的不可否认性。
两者可以且应当共存:S/MIME 签名后的邮件再由 MTA 施加 DKIM 签名,互不冲突。但要注意先后——DKIM 签名之后如果再有环节改写正文,DKIM 也会失效。邮件列表场景下两种签名往往同时被破坏,这正是 ARC(RFC 8617)试图缓解的问题,但 ARC 只恢复域级认证的判定依据,无法修复被破坏的端到端用户签名。对必须使用端到端签名的业务流程,应避免经由会改写内容的邮件列表转发。
参考:IETF RFC 8551《S/MIME Version 4.0 Message Specification》(Standards Track,2019-04);多部分安全框架 RFC 1847;三重包装 RFC 2634《Enhanced Security Services for S/MIME》;DKIM 见 RFC 6376
