You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用SslStream.AuthenticateAsClient报握手因意外包格式失败错误

问题解决:C# 使用SslStream连接Office365 SMTP 587端口握手失败问题

根因说明

该错误的根本原因是SMTP端口TLS模式匹配错误:
Office365的smtp.office365.com 587端口采用**显式TLS(STARTTLS)**机制,TCP连接建立后首先走明文SMTP协议交互,等客户端发送STARTTLS命令后才会切换到TLS加密通道。原有代码在TCP连接建立后直接发起TLS握手,服务器此时返回的是明文SMTP响应报文,不符合TLS报文格式,因此触发The handshake failed due to an unexpected packet format.错误。

修复步骤

  • 首先完成TCP连接后的明文SMTP初始交互:
    1. 读取服务器返回的220 <服务标识>初始响应
    2. 发送EHLO yourdomain.com命令,读取250响应,确认服务器支持STARTTLS扩展
    3. 发送STARTTLS命令,读取服务器返回的220 2.0.0 SMTP server ready响应,确认可以开始TLS握手
  • 完成上述交互后再调用AuthenticateAsClient方法执行TLS握手,后续SMTP命令全部走加密的SslStream传输

修正后的核心代码示例

class Program
{
    static void Main(string[] args)
    {
        try
        {
            string Server = "smtp.office365.com";
            int port = 587;
            System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (var tcp = new System.Net.Sockets.TcpClient())
            {
                tcp.Connect(Server, port);
                var baseStream = tcp.GetStream();
                StreamReader reader = new StreamReader(baseStream);
                StreamWriter writer = new StreamWriter(baseStream) { NewLine = "\r\n", AutoFlush = true };

                // 读取初始220响应
                var resp = reader.ReadLine();
                if (!resp.StartsWith("220")) throw new Exception("SMTP服务连接失败");

                // 发送EHLO命令
                writer.WriteLine("EHLO yourdomain.com");
                // 读取所有250响应行
                while ((resp = reader.ReadLine()) != null)
                {
                    if (resp.StartsWith("250 ")) break;
                    if (!resp.StartsWith("250-")) throw new Exception("EHLO执行失败");
                }

                // 发送STARTTLS命令
                writer.WriteLine("STARTTLS");
                resp = reader.ReadLine();
                if (!resp.StartsWith("220")) throw new Exception("STARTTLS不被服务端支持");

                // 此时再初始化SslStream执行握手
                System.Net.Security.RemoteCertificateValidationCallback remoteCertCallback =
                    (Object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors Errors) => true;

                System.Net.Security.SslStream ssl =
                    new System.Net.Security.SslStream(baseStream, false, remoteCertCallback, SelectLocalCertificate);

                // 此处握手不再报错
                ssl.AuthenticateAsClient(Server, null, System.Security.Authentication.SslProtocols.Tls12, true);

                // 后续SMTP命令通过ssl传输,需要重新发送EHLO
                StreamReader sslReader = new StreamReader(ssl);
                StreamWriter sslWriter = new StreamWriter(ssl) { NewLine = "\r\n", AutoFlush = true };
                sslWriter.WriteLine("EHLO yourdomain.com");
                // 后续可继续执行身份验证、发送邮件等逻辑
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
        Console.Read();
    }

    // 原有GetLocalCertificates、SelectLocalCertificate方法保持不变即可
    public static X509Certificate2Collection GetLocalCertificates()
    {
        X509Certificate2Collection Certificates = null;
        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        try
        {
            store.Open(OpenFlags.ReadOnly);
            Certificates = store.Certificates;
        }
        finally
        {
            store.Close();
        }
        return Certificates;
    }

    public static X509Certificate SelectLocalCertificate(
        object sender,
        string targetHost,
        X509CertificateCollection localCertificates,
        X509Certificate remoteCertificate,
        string[] acceptableIssuers)
    {
        if (acceptableIssuers != null &&
            acceptableIssuers.Length > 0 &&
            localCertificates != null &&
            localCertificates.Count > 0)
        {
            foreach (X509Certificate certificate in localCertificates)
            {
                string issuer = certificate.Issuer;
                if (Array.IndexOf(acceptableIssuers, issuer) != -1)
                    return certificate;
            }
        }
        if (localCertificates != null &&
            localCertificates.Count > 0)
            return localCertificates[0];
        return null;
    }
}

补充说明

如果需要直接使用隐式TLS,可以切换到465端口,无需走STARTTLS流程,TCP连接建立后直接发起TLS握手即可,不过需要确认你所在的网络环境开放了465端口访问权限。

内容的提问来源于stack exchange,提问作者Leon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 14:57:00