端口587连接smtp.gmail.com时SSL握手失败,端口465正常求助
解决Gmail SMTP端口587连接时的SSLException异常
这个问题的核心是你混淆了SMTP两种加密端口的工作方式:
- 端口465是
SMTPS协议,连接建立时就直接启动SSL/TLS加密,所以你的SSLSocket代码用它没问题 - 端口587是标准SMTP端口,要求先建立明文连接,再通过
STARTTLS命令主动切换到加密模式,直接用SSLSocket连它就会因为对方不识别SSL握手报文而报错
下面是修正后的代码,我标注了关键步骤:
package smtpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.net.Socket; class TLS_Mime_G { static final int PORT = 587; static String REMOTEHOST = "smtp.gmail.com"; public static void main(String[] args) throws IOException, NoSuchAlgorithmException, KeyManagementException { // 第一步:先建立普通明文Socket连接到587端口 Socket plainSocket = new Socket(REMOTEHOST, PORT); BufferedReader in = new BufferedReader(new InputStreamReader(plainSocket.getInputStream())); OutputStreamWriter out = new OutputStreamWriter(plainSocket.getOutputStream()); // 读取服务器初始响应 System.out.println("Server response: " + in.readLine()); // 第二步:发送EHLO命令(SMTP握手必备) out.write("EHLO localhost\r\n"); out.flush(); // 读取服务器EHLO响应(简化处理,实际可循环读取直到结束) String line; while ((line = in.readLine()) != null && !line.startsWith("250 ")) { System.out.println("Server EHLO response: " + line); } System.out.println("Final EHLO response: " + line); // 第三步:发送STARTTLS命令,请求切换到加密连接 out.write("STARTTLS\r\n"); out.flush(); System.out.println("STARTTLS response: " + in.readLine()); // 第四步:将普通Socket包装成SSLSocket,启动SSL握手 SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslSocket = (SSLSocket) ssf.createSocket( plainSocket, REMOTEHOST, PORT, true); // 用已有的明文Socket包装 sslSocket.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); // 指定安全的协议版本 sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites()); sslSocket.addHandshakeCompletedListener(new MyTLSHandshakeListener()); sslSocket.startHandshake(); // 现在执行握手就不会报错了 System.out.println("Connected to " + sslSocket.getRemoteSocketAddress()); // 后续可继续发送SMTP命令(比如AUTH登录等) // ... // 关闭资源 sslSocket.close(); plainSocket.close(); } } class MyTLSHandshakeListener implements HandshakeCompletedListener { public void handshakeCompleted(HandshakeCompletedEvent e) { System.out.println("Handshake successful!"); System.out.println("Cipher suite used: " + e.getCipherSuite()); } }
另外补充几个实用注意点:
- 建议指定更安全的TLS版本(比如TLSv1.2及以上),避免老旧的SSLv3等不安全协议
- 连接Gmail SMTP需要开启低安全性应用权限(如果开启了两步验证,需要使用App密码)
- 实际生产环境中,更推荐使用JavaMail API来处理SMTP通信,它已经封装了这些复杂的握手和加密逻辑,不用手动处理Socket
内容的提问来源于stack exchange,提问作者Shashank




