使用BouncyCastle发送S/MIME加密邮件遇实例类型异常求助
问题描述
发送S/MIME加密邮件时持续抛出以下异常:
javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.IOException: javax.mail.MessagingException: Cannot write this object. It has to be an instance of iaik.security.smime.EncryptedContent or iaik.security.smime.SignedContent!
环境与操作细节
- 使用Bouncy Castle作为安全提供者:测试输出显示版本v1.69,实际引入1.71的jdk18on-171系列包
- 参照
org.bouncycastle.mail.smime.examples.CreateEncryptedMail编写代码,额外操作:- 从文件读取证书
- 配置Mailcap:
private void setDefaultMailcap() { final MailcapCommandMap mailcap = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); mailcap.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); mailcap.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); mailcap.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); mailcap.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); mailcap.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); CommandMap.setDefaultCommandMap(mailcap); } - 配置SMTP服务器:
props.put("mail.smtp.host", host); props.put("mail.smtp.port", port); Session session = Session.getInstance(props, new Authenticator() { protected javax.mail.PasswordAuthentication getPasswordAuthentication() { return new javax.mail.PasswordAuthentication(user, password); } }); - 发送代码:
final Transport transport = session.getTransport("smtp"); transport.send(body);
- 依赖库:activation.jar、bcmail-jdk18on-171.jar、bcpkix-jdk18on-171.jar、bcprov-ext-jdk18on-171.jar、bcprov-jdk18on-171.jar、bcutil-jdk18on-171.jar、commons-codec-1.15.jar、commons-io-2.11.0.jar、junit.jar、mail.jar
完整异常栈
javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.IOException: javax.mail.MessagingException: Cannot write this object. It has to be an instance of iaik.security.smime.EncryptedContent or iaik.security.smime.SignedContent!
com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1350)
javax.mail.Transport.send0(Transport.java:231)
javax.mail.Transport.send(Transport.java:100)
问题根源与解决办法
1. 移除IAIK库依赖或残留
异常中提到的iaik.security.smime类属于IAIK S/MIME库,而非Bouncy Castle。说明项目或运行环境中存在IAIK相关组件,导致JavaMail优先使用了IAIK的内容处理器。
- 检查项目依赖,删除所有IAIK相关jar包(如
iaik_jce.jar、iaik_smime.jar等) - 检查JRE的
lib/ext目录,移除其中的IAIK文件 - 用依赖管理工具(Maven/Gradle)排查并排除间接引入的IAIK依赖
2. 重构Mailcap配置,避免原有配置干扰
修改Mailcap配置逻辑,新建命令映射而非修改默认实例,确保Bouncy Castle处理器优先级最高:
private void setDefaultMailcap() { // 新建MailcapCommandMap,彻底替换原有配置 MailcapCommandMap mailcap = new MailcapCommandMap(); mailcap.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); mailcap.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); mailcap.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); mailcap.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); mailcap.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); CommandMap.setDefaultCommandMap(mailcap); }
3. 优先注册Bouncy Castle安全提供者
在代码启动阶段将Bouncy Castle注册为最高优先级的安全提供者:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; // 在应用初始化时执行 Security.insertProviderAt(new BouncyCastleProvider(), 1);
4. 确认加密对象类型为Bouncy Castle实现
确保加密后的邮件对象使用Bouncy Castle的SMIMEEnveloped生成,而非IAIK类型。示例流程:
// 创建原始MimeMessage MimeMessage originalMsg = new MimeMessage(session); originalMsg.setFrom(new InternetAddress(sender)); originalMsg.setRecipient(Message.RecipientType.TO, new InternetAddress(recipient)); originalMsg.setSubject("S/MIME Encrypted Test"); originalMsg.setText("Test content"); // 使用Bouncy Castle生成加密内容 SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC")); MimeBodyPart encryptedPart = gen.generate(originalMsg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider("BC").build()); // 封装为可发送的MimeMessage MimeMessage encryptedMsg = new MimeMessage(session); encryptedMsg.setContent(encryptedPart.getContent(), encryptedPart.getContentType()); encryptedMsg.setHeader("Content-Disposition", "attachment; filename=\"encrypted.msg\""); // 发送加密后的消息 transport.send(encryptedMsg);
5. 清理旧版本Bouncy Castle残留
测试输出显示版本v1.69但实际引入1.71,说明存在旧版本BC包:
- 检查项目依赖,排除所有低于1.71版本的BC组件
- 检查应用服务器/容器的类路径,删除旧版本BC jar
内容的提问来源于stack exchange,提问作者user20144981

