Go语言TCP连接PostgreSQL遇SSL请求后接收未加密数据错误
问题分析与解决
问题根源
你直接通过TCP连接PostgreSQL服务器后就发送查询语句,但服务器要求使用SSL加密连接,而你的代码完全跳过了SSL握手流程,直接发送明文查询,这就导致服务器触发FATAL错误,返回的"N"是PostgreSQL在SSL协商阶段的响应(表示服务器期望SSL握手,但收到了非法数据)。
服务器日志翻译如下:
2023-02-22 22:59:49.834 PKT [54802] FATAL: SSL请求后收到未加密数据
2023-02-22 22:59:49.834 PKT [54802] DETAIL: 这可能是客户端软件bug,也可能是中间人攻击的迹象。
解决方法
方法1:使用PostgreSQL官方驱动(推荐)
不要手动实现TCP连接和协议,直接用成熟的Go驱动,它们会自动处理SSL握手、认证、报文解析等所有细节。比如github.com/lib/pq或者github.com/jackc/pgx:
package main import ( "database/sql" "fmt" _ "github.com/lib/pq" ) func main() { // 连接字符串根据你的数据库配置调整,sslmode设为服务器要求的模式(比如require、verify-full) connStr := "user=你的用户名 dbname=你的数据库名 sslmode=require host=localhost port=5432" db, err := sql.Open("postgres", connStr) if err != nil { fmt.Printf("连接失败:%v\n", err) return } defer db.Close() // 测试查询 rows, err := db.Query("SELECT 1") if err != nil { fmt.Printf("查询失败:%v\n", err) return } defer rows.Close() var num int for rows.Next() { err := rows.Scan(&num) if err != nil { fmt.Printf("结果解析失败:%v\n", err) return } fmt.Printf("查询结果:%d\n", num) } }
方法2:手动实现SSL握手(仅用于学习,不推荐)
如果一定要手动处理TCP连接,必须先完成SSL握手:
- 连接建立后,先发送PostgreSQL的SSL请求标识报文
- 确认服务器同意SSL后,建立TLS连接
- 再通过TLS连接发送查询
示例代码:
package main import ( "crypto/tls" "fmt" "net" ) func main() { // 建立TCP连接 conn, err := net.Dial("tcp", "localhost:5432") if err != nil { fmt.Printf("TCP连接失败:%v\n", err) return } defer conn.Close() // 发送PostgreSQL SSL请求报文 sslReq := []byte{0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f} _, err = conn.Write(sslReq) if err != nil { fmt.Printf("发送SSL请求失败:%v\n", err) return } // 读取服务器响应 resp := make([]byte, 1) _, err = conn.Read(resp) if err != nil { fmt.Printf("读取SSL响应失败:%v\n", err) return } if string(resp) != "S" { fmt.Printf("服务器不支持SSL,响应:%s\n", resp) return } // 建立TLS连接 tlsConn := tls.Client(conn, &tls.Config{ ServerName: "localhost", // 替换为你的服务器主机名 }) err = tlsConn.Handshake() if err != nil { fmt.Printf("TLS握手失败:%v\n", err) return } defer tlsConn.Close() // 发送查询语句 query := "SELECT 1;" _, err = tlsConn.Write([]byte(query)) if err != nil { fmt.Printf("发送查询失败:%v\n", err) return } // 读取响应 buf := make([]byte, 50000) n, err := tlsConn.Read(buf) if err != nil { fmt.Printf("读取响应失败:%v\n", err) return } fmt.Printf("收到响应:%s\n", string(buf[:n])) }
注意事项
手动实现PostgreSQL协议需要处理大量细节(比如报文的长度标识、认证流程、数据解析等),很容易出错,实际项目中强烈推荐使用官方驱动。
内容的提问来源于stack exchange,提问作者Kamlesh Kumar
相关产品推荐
相关产品推荐

