在 java 程序中发出 STARTTLS 命令后,startHandshake 出现不支持或无法识别的 ssl-message 异常

Unsupported or unrecognized ssl-message exception at startHandshake after issuing STARTTLS command in a java program

我在 java 中编写了一个简单的 e-mail 客户端,但没有使用 JavaMail API,根据我从文档中读到的内容,似乎有必要将消息拆分为头部和 body 并在单个 MIME-parts 中发送,这会使我的应用程序变得更加复杂。对于 SSL 端口 (465) 一切正常,但现在我正在尝试将客户端与仅支持端口 587 上的 STARTTLS 的不同提供商一起使用。以下代码是我编写的 class 测试访问权限的代码服务器。

private static DataOutputStream dos; //Streams to communicate with the server
private static DataInputStream dis;

public static void main(String[] args) {
    try {
        Socket socket = new Socket("mail.arcor.de", 587);
        dos = new DataOutputStream(socket.getOutputStream());
        dis = new DataInputStream(socket.getInputStream());

        new Thread(() -> {readInputRun();}).start(); //see below, every input from the server is printed to the standard output.

        dos.write(("EHLO " + InetAddress.getLocalHost().getHostAddress() + "\r\n").getBytes());

        dos.write("STARTTLS\r\n".getBytes());

        SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(socket, "mail.arcor.de", 587, true);
        dos = new DataOutputStream(sslSocket.getOutputStream());
        dis = new DataInputStream(sslSocket.getInputStream());

        sslSocket.startHandshake();

        dos.write(("EHLO " + InetAddress.getLocalHost().getHostAddress() + "\r\n").getBytes());

        dos.write("QUIT\r\n".getBytes());

        Thread.sleep(5000);
        dis.close();
        dos.close();
        socket.close();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

private static void readInputRun() {
    try {
        int input;
        while((input = dis.read()) >= 0) {
            System.out.print((char) input);
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}

每次执行时,都会出现以下异常:

javax.net.ssl.SSLException: Unsupported or unrecognized SSL message
at java.base/sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:416)
at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:173)
at java.base/sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1031)
at java.base/sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973)
at java.base/sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1402)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1429)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at accessTest.AccessTestSTARTTLS.main(AccessTestSTARTTLS.java:31)

它在第 31 行被抛出,也就是 'sslSocket.startHandshake();',所以不知何故握手没有成功。我是否在错误的点或以错误的方式启动它?

在此先感谢您的帮助。

编辑:正如评论中所建议的,我启用了日志并发现了以下内容: httpslog。在第五行也是最后一行中有一个不可读的字符,显然是客户端问候的一部分,所以我的计算机发送了它。为什么会这样,我该如何阻止?

需要说明的是,这个答案的主要目的是将问题标记为已回答,因为实际上在问题的评论中可以找到解决方案。问题出在readInputRun()读取部分ServerHello,所以让读取线程在握手前自己中断,解决后再重启:

dos.write("STARTTLS\r\n".getBytes());

SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(socket, "mail.arcor.de", 587, true);
dos = new DataOutputStream(sslSocket.getOutputStream());
dis = new DataInputStream(sslSocket.getInputStream());

sslSocket.startHandshake();

new Thread(() -> {readInputRun();}).start();

dos.write(("EHLO " + InetAddress.getLocalHost().getHostAddress() + "\r\n").getBytes());

dos.write("QUIT\r\n".getBytes());

和:

private static void readInputRun() {
    try {
        int input;
        String allInput = "";
        while((input = dis.read()) >= 0) {
            allInput += (char) input;
            System.out.print(allInput.charAt(allInput.length() - 1));

            if(allInput.endsWith("Start TLS\r\n")) {
                break;
            }
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}