PHP中发送S/MIME加密邮件:MIME头显示smime.p7m但下载附件为带时间戳EDI文件的实现方法
PHP中发送S/MIME加密邮件:MIME头显示smime.p7m但下载附件为带时间戳EDI文件的实现方法
嘿,这个需求我之前对接类似EDI系统的时候刚好碰到过!本质上就是要给附件的Content-Disposition头玩点小技巧——同时放两个文件名参数,一个满足ABF的系统检查要求,另一个让用户下载时看到业务用的EDI文件名。下面给你一步步拆解实现方法:
一、先把S/MIME加密流程走对
首先得纠正一个可能的误区:你现在直接把原EDI文件添加到邮件里是不对的,ABF要求的是**加密后的S/MIME信封(也就是.p7m格式的加密文件)**作为附件。正确的处理流程应该是先签名再加密,生成标准的p7m文件:
// 定义文件路径 $ediFilePath = 'AA*****_202504021256.edi'; // 临时文件:存储签名后的内容 $signedTempFile = tempnam(sys_get_temp_dir(), 'signed_edi_'); // 最终加密后的p7m文件 $encryptedP7mFile = tempnam(sys_get_temp_dir(), 'abf_edi_') . '.p7m'; // 1. 对EDI文件进行数字签名(用你的企业证书) openssl_pkcs7_sign( $ediFilePath, $signedTempFile, 'file:///path/to/your/company-cert.pem', ['file:///path/to/your/private-key.pem', 'your-key-password'], // 私钥路径+密码 [], PKCS7_DETACHED // 分离式签名,符合S/MIME标准 ); // 2. 用ABF的公钥对签名后的文件加密,生成p7m信封 openssl_pkcs7_encrypt( $signedTempFile, $encryptedP7mFile, 'file:///path/to/abf-public-cert.pem', [], PKCS7_BINARY | PKCS7_ENVELOPE // 二进制模式+信封加密 ); // 清理临时签名文件(可选,节省磁盘空间) unlink($signedTempFile);
二、实现“双文件名”的核心操作
ABF要求MIME头里的filename必须是smime.p7m,但我们需要让用户下载时显示业务用的EDI文件名。这时候要用到RFC 5987定义的filename*参数——邮件客户端会优先用这个参数作为下载文件名,而ABF的系统检查时只会读取filename参数,完美契合需求。
用PHPMailer的话,不能直接依赖addAttachment的默认参数,得手动修改附件的头信息,具体代码如下:
// 初始化PHPMailer实例 $mail = new PHPMailer\PHPMailer\PHPMailer(); $mail->isSMTP(); // 配置你的SMTP信息(根据自己的服务商来) $mail->Host = 'your-smtp-server.com'; $mail->SMTPAuth = true; $mail->Username = 'your-email@domain.com'; $mail->Password = 'your-email-password'; $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS; $mail->Port = 465; // 设置邮件基础信息 $mail->setFrom('your-sender-email@domain.com', 'Your Company Name'); $mail->addAddress('abf-recipient@abf-domain.com'); $mail->Subject = 'EDI Submission - ' . date('YmdHis'); // 加载加密后的p7m文件内容,添加为附件 $p7mContent = file_get_contents($encryptedP7mFile); // 这里第二个参数填smime.p7m,满足ABF对MIME头的要求 $attachmentIndex = $mail->addStringAttachment( $p7mContent, 'smime.p7m', PHPMailer\PHPMailer\PHPMailer::ENCODING_BASE64, 'application/x-pkcs7-mime; smime-type=enveloped-data' ); // 核心操作:修改附件的Content-Disposition头,添加filename*参数 $targetEdiFileName = 'AA*****_202504021256.edi'; // 对文件名进行URL编码,避免特殊字符乱码 $encodedFileName = rawurlencode($targetEdiFileName); // 找到对应的附件对象(PHPMailer的附件索引从0开始,所以要减1) $attachment = $mail->getAttachment($attachmentIndex - 1); // 重新构造Content-Disposition头,同时包含两个文件名参数 $attachment->disposition = "attachment; filename=\"smime.p7m\"; filename*=UTF-8''{$encodedFileName}"; // 发送邮件 if (!$mail->send()) { echo '邮件发送失败: ' . $mail->ErrorInfo; } else { echo '邮件发送成功,已提交给ABF'; } // 清理临时p7m文件 unlink($encryptedP7mFile);
三、验证环节不能少
配置完一定要做这几步验证,确保万无一失:
- 发送测试邮件后,查看邮件的原始MIME源码,确认
Content-Disposition头是类似这样的:
Content-Disposition: attachment; filename="smime.p7m"; filename*=UTF-8''AA%2A%2A%2A%2A%2A_202504021256.edi
- 用普通邮件客户端(比如Outlook、Thunderbird)下载附件,确认文件名是
AA*****_202504021256.edi - 联系ABF的技术支持,或者查看他们的接收日志,确认系统能正常解析这个附件
常见坑点提醒
- 绝对不能直接发原EDI文件:ABF的系统只认加密后的p7m文件,原文件发过去会直接被拒收
- 文件名编码要到位:
filename*参数必须用rawurlencode处理,不然中文或特殊字符会乱码 - 一定要用PHPMailer的最新版本:旧版本可能不支持修改附件的disposition属性
内容来源于stack exchange
相关产品推荐
相关产品推荐

