调用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初始交互:
- 读取服务器返回的
220 <服务标识>初始响应 - 发送
EHLO yourdomain.com命令,读取250响应,确认服务器支持STARTTLS扩展 - 发送
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
相关产品推荐
相关产品推荐

