stcp

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2026 License: MIT Imports: 23 Imported by: 0

README

STCP 🔐

A lightweight encrypted TCP connection library for Go, providing end-to-end encryption with ECDH key exchange and AES-256-GCM.

English | 中文


English

Features
  • 🔒 End-to-End Encryption - X25519 ECDH key exchange + AES-256-GCM
  • 📜 Certificate-based Authentication - Mutual TLS authentication with self-signed certificates
  • Nonce-enhanced Key Derivation - SHA256(ECDH key || clientNonce || serverNonce)
  • 🌐 Standard net.Conn Interface - Drop-in replacement for standard net.Conn
  • 🚀 High Performance - Minimal overhead, optimized for low latency
  • 🔄 Memory Pool - sync.Pool for efficient buffer reuse
  • 📦 Minimal Dependencies - Zero external dependencies (pure Go)
  • ⏱️ Deadline Support - Full support for read/write deadlines
  • 🔑 Session Recovery - Quick session resumption without full handshake (10 min TTL)
Installation
go get github.com/wsyqn6/stcp

Requires Go 1.26.1 or later.

Quick Start
Server
package main

import (
	"log"
	"io"
	"github.com/wsyqn6/stcp"
)

func main() {
	srv, err := stcp.NewServer("server.crt", "server.key")
	if err != nil {
		log.Fatalf("NewServer failed: %v", err)
	}

	lis, err := srv.Listen("tcp", ":13579")
	if err != nil {
		log.Fatalf("Listen failed: %v", err)
	}

	log.Printf("server started on %s", lis.Addr())

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Printf("Accept failed: %v", err)
			continue
		}
		go handle(conn)
	}
}

func handle(conn io.ReadWriteCloser) {
	defer conn.Close()

	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		log.Printf("Read failed: %v", err)
		return
	}

	log.Printf("received: %s", string(buf[:n]))
	conn.Write([]byte("echo: " + string(buf[:n])))
}
Client
package main

import (
	"log"
	"github.com/wsyqn6/stcp"
)

func main() {
	conn, err := stcp.Dial("tcp", "localhost:13579", "root.crt")
	if err != nil {
		log.Fatalf("Dial failed: %v", err)
	}
	defer conn.Close()

	// Send message
	_, err = conn.Write([]byte("hello"))
	if err != nil {
		log.Fatalf("Write failed: %v", err)
	}

	// Receive response
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		log.Fatalf("Read failed: %v", err)
	}

	log.Printf("received: %s", string(buf[:n]))
}
Generate Certificates
package main

import (
	"time"
	"github.com/wsyqn6/stcp"
)

func main() {
	// Generate self-signed certificate (valid for 1 year)
	certPEM, keyPEM, err := stcp.GenerateSelfSignedCert(
		"server",
		time.Now(),
		time.Now().Add(365*24*time.Hour),
	)
	if err != nil {
		panic(err)
	}

	// Save to files
	// Use certPEM and keyPEM for server
}
API Reference
Function Description
NewServer(certFile, keyFile string) (*Server, error) Create server with certificate and key files
NewServerFromMem(cert *x509.Certificate, key []byte) (*Server, error) Create server from memory
(s *Server) Listen(network, addr string) (net.Listener, error) Start listening on given network and address
Dial(network, addr string, rootCertFile string) (*Conn, error) Dial with root CA certificate file
DialWithCert(network, addr string, cert *x509.Certificate) (*Conn, error) Dial with server certificate
(c *Conn) Read(b []byte) (int, error) Read decrypted data
(c *Conn) Write(b []byte) (int, error) Write encrypted data
(c *Conn) Close() error Close connection
(c *Conn) LocalAddr() net.Addr Local address
(c *Conn) RemoteAddr() net.Addr Remote address
(c *Conn) SetDeadline(t time.Time) error Set read/write deadline
(c *Conn) SetReadDeadline(t time.Time) error Set read deadline
(c *Conn) SetWriteDeadline(t time.Time) error Set write deadline
(c *Conn) Recover(kid uint64, data []byte) error Recover session with kid and optional data
Certificate Functions
Function Description
LoadPemCertficate(file string) (*x509.Certificate, error) Load certificate from PEM file
LoadPemCertKey(file string) (crypto.PrivateKey, error) Load private key from PEM file
GenerateSelfSignedCert(commonName string, notBefore, notAfter time.Time) ([]byte, []byte, error) Generate self-signed certificate
Protocol
Handshake Flow
  1. Client sends: [4B "STCP"] [32B clientPubKey] [32B clientNonce]
  2. Server validates, generates ECDH shared key
  3. Server responds: [2B certLen] [cert] [32B serverPubKey] [512B sign] [32B serverNonce] [8B kid]
  4. Both derive encryption key: SHA256(ECDH key || clientNonce || serverNonce)
  5. Server stores session state (TTL: 10 minutes)
Session Recovery Flow
  1. Client sends: [Type: Recover] [8B kid] [encrypted data (optional)]
  2. Server validates kid and restores session key
  3. If data present, decrypts and processes immediately
  4. Connection continues in normal data mode
Packet Format
[1B Ver] [1B Type] [1B Status] [1B Reserved] [4B Length] [Body]
  • Ver: Protocol version (1)
  • Type: Packet type (1=handshake, 2=recover, 3=data)
  • Status: Success/fail status
  • Reserved: Reserved for future use
  • Length: Body length (4 bytes)
  • Body: Encrypted payload
Security Design
Key Exchange
  • Uses X25519 ECDH for key exchange
  • Each session generates ephemeral key pairs
  • No long-term key material stored
Key Derivation

The final encryption key is derived using:

key = SHA256(ECDH_shared_key || clientNonce || serverNonce)

This ensures:

  • Forward secrecy (session keys not derived from long-term keys)
  • Unique key per session (nonces add randomness)
  • Resistance against replay attacks
Encryption
  • AES-256-GCM authenticated encryption
  • Random nonce per message (unique per write)
  • 16-byte authentication tag
Benchmark
go test -bench=. -benchtime=1s ./...

Typical results (local loopback):

BenchmarkConnWriteRead-8          100000   15000 ns/op
BenchmarkConnParallel-8           500000    3200 ns/op
Why STCP?
Feature STCP TLS secureio
Protocol Custom TCP Standard TLS Custom
Key Exchange ECDH X25519 RSA/ECDHE ECDH
Encryption AES-256-GCM AES-GCM XChaCha20
Dependencies None None None
net.Conn
License

MIT License - see LICENSE for details.


中文

功能特性
  • 🔒 端到端加密 - X25519 ECDH 密钥交换 + AES-256-GCM
  • 📜 证书认证 - 自签名证书双向 TLS 认证
  • Nonce 增强密钥派生 - SHA256(ECDH密钥 || 客户端Nonce || 服务端Nonce)
  • 🌐 标准 net.Conn 接口 - 可直接替代标准 net.Conn
  • 🚀 高性能 - 极低开销,优化低延迟场景
  • 🔄 内存池 - sync.Pool 高效缓冲区复用
  • 📦 最小依赖 - 零外部依赖(纯 Go 实现)
  • ⏱️ 超时支持 - 完整支持读写超时
  • 🔑 会话恢复 - 无需完整握手快速恢复会话(10分钟有效期)
安装
go get github.com/wsyqn6/stcp

需要 Go 1.26.1 或更高版本。

快速开始
服务端
package main

import (
	"log"
	"io"
	"github.com/wsyqn6/stcp"
)

func main() {
	srv, err := stcp.NewServer("server.crt", "server.key")
	if err != nil {
		log.Fatalf("创建服务器失败: %v", err)
	}

	lis, err := srv.Listen("tcp", ":13579")
	if err != nil {
		log.Fatalf("监听失败: %v", err)
	}

	log.Printf("服务器启动于 %s", lis.Addr())

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Printf("接受连接失败: %v", err)
			continue
		}
		go handle(conn)
	}
}

func handle(conn io.ReadWriteCloser) {
	defer conn.Close()

	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		log.Printf("读取失败: %v", err)
		return
	}

	log.Printf("收到: %s", string(buf[:n]))
	conn.Write([]byte("回显: " + string(buf[:n])))
}
客户端
package main

import (
	"log"
	"github.com/wsyqn6/stcp"
)

func main() {
	conn, err := stcp.Dial("tcp", "localhost:13579", "root.crt")
	if err != nil {
		log.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	// 发送消息
	_, err = conn.Write([]byte("你好"))
	if err != nil {
		log.Fatalf("发送失败: %v", err)
	}

	// 接收响应
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		log.Fatalf("接收失败: %v", err)
	}

	log.Printf("收到: %s", string(buf[:n]))
}
生成证书
package main

import (
	"time"
	"github.com/wsyqn6/stcp"
)

func main() {
	// 生成自签名证书(有效期1年)
	certPEM, keyPEM, err := stcp.GenerateSelfSignedCert(
		"server",
		time.Now(),
		time.Now().Add(365*24*time.Hour),
	)
	if err != nil {
		panic(err)
	}

	// 保存到文件
	// 使用 certPEM 和 keyPEM 启动服务器
}
API 参考
函数 描述
NewServer(certFile, keyFile string) (*Server, error) 使用证书和密钥文件创建服务器
NewServerFromMem(cert *x509.Certificate, key []byte) (*Server, error) 从内存创建服务器
(s *Server) Listen(network, addr string) (net.Listener, error) 在指定网络和地址上开始监听
Dial(network, addr string, rootCertFile string) (*Conn, error) 使用根CA证书文件连接
DialWithCert(network, addr string, cert *x509.Certificate) (*Conn, error) 使用服务器证书连接
(c *Conn) Read(b []byte) (int, error) 读取解密后的数据
(c *Conn) Write(b []byte) (int, error) 写入加密数据
(c *Conn) Close() error 关闭连接
(c *Conn) LocalAddr() net.Addr 本地地址
(c *Conn) RemoteAddr() net.Addr 远程地址
(c *Conn) SetDeadline(t time.Time) error 设置读写超时
(c *Conn) SetReadDeadline(t time.Time) error 设置读取超时
(c *Conn) SetWriteDeadline(t time.Time) error 设置写入超时
(c *Conn) Recover(kid uint64, data []byte) error 使用 kid 恢复会话,可携带数据
证书相关函数
函数 描述
LoadPemCertficate(file string) (*x509.Certificate, error) 从PEM文件加载证书
LoadPemCertKey(file string) (crypto.PrivateKey, error) 从PEM文件加载私钥
GenerateSelfSignedCert(commonName string, notBefore, notAfter time.Time) ([]byte, []byte, error) 生成自签名证书
协议设计
握手流程
  1. 客户端发送: [4B "STCP"] [32B 客户端公钥] [32B 客户端Nonce]
  2. 服务端验证,生成 ECDH 共享密钥
  3. 服务端响应: [2B 证书长度] [证书] [32B 服务端公钥] [512B 签名] [32B 服务端Nonce] [8B 密钥ID]
  4. 双方派生加密密钥: SHA256(ECDH密钥 || 客户端Nonce || 服务端Nonce)
  5. 服务端存储会话状态(有效期:10分钟)
会话恢复流程
  1. 客户端发送: [类型: Recover] [8B 密钥ID] [加密数据(可选)]
  2. 服务端验证密钥ID并恢复会话密钥
  3. 如有数据,立即解密处理
  4. 连接进入正常数据模式
数据包格式
[1B 版本] [1B 类型] [1B 状态] [1B 保留] [4B 长度] [数据体]
  • 版本: 协议版本 (1)
  • 类型: 数据包类型(1=握手, 2=恢复, 3=数据)
  • 状态: 成功/失败状态
  • 保留: 保留字段
  • 长度: 数据体长度 (4字节)
  • 数据体: 加密载荷
安全设计
密钥交换
  • 使用 X25519 ECDH 进行密钥交换
  • 每个会话生成临时密钥对
  • 不存储长期密钥材料
密钥派生

最终加密密钥通过以下方式派生:

密钥 = SHA256(ECDH共享密钥 || 客户端Nonce || 服务端Nonce)

确保:

  • 前向保密(会话密钥不来自长期密钥)
  • 每会话唯一(Nonce增加随机性)
  • 抗重放攻击
加密
  • AES-256-GCM 认证加密
  • 每个消息使用随机Nonce
  • 16字节认证标签
性能测试
go test -bench=. -benchtime=1s ./...

典型结果(本地回环):

BenchmarkConnWriteRead-8          100000   15000 ns/op
BenchmarkConnParallel-8           500000    3200 ns/op
为什么选择 STCP?
特性 STCP TLS secureio
协议 自定义TCP 标准TLS 自定义
密钥交换 ECDH X25519 RSA/ECDHE ECDH
加密 AES-256-GCM AES-GCM XChaCha20
依赖
net.Conn
许可证

MIT 许可证 - 详见 LICENSE


📚 GoDoc🐛 Issues

Documentation

Index

Constants

View Source
const (
	Version       = 1
	HeaderLength  = 10
	ECDHKeyLength = 32
	NonceLength   = 32
	Stcp          = "STCP"
	MaxPacketSize = 64 * 1024
	MaxBodySize   = MaxPacketSize - HeaderLength
)
View Source
const SessionTimeout = 10 * time.Minute

Variables

View Source
var (
	ErrServerClosed     = errors.New("server closed")
	ErrAESGCMNotInit    = errors.New("aesgcm not init")
	ErrCipherTooShort   = errors.New("cipher too short")
	ErrFailResponse     = errors.New("fail response")
	ErrEmptyBody        = errors.New("empty body")
	ErrInvalidVersion   = errors.New("invalid version")
	ErrBodyToolong      = errors.New("body too long")
	ErrUnexpectedPrefix = errors.New("unexpected prefix")
	ErrPacketTooLarge   = errors.New("packet too large")
	ErrSessionNotFound  = errors.New("session not found")
	ErrSessionExpired   = errors.New("session expired")
	ErrInvalidBody      = errors.New("invalid body")
)
View Source
var NowTime = time.Now

Functions

func DeriveKey

func DeriveKey(ecdhKey, clientNonce, serverNonce []byte) []byte

func GenerateSelfSignedCert

func GenerateSelfSignedCert(commonName string, notBefore, notAfter time.Time) ([]byte, []byte, error)

func LoadPemCertKey

func LoadPemCertKey(file string) (crypto.PrivateKey, error)

func LoadPemCertficate

func LoadPemCertficate(file string) (*x509.Certificate, error)

func NowMs

func NowMs() int64

func ReadFile

func ReadFile(name string) ([]byte, error)

func Send

func Send(w io.Writer, ht, status byte, body ...[]byte) error

func SendFail

func SendFail(w io.Writer, ht byte, body ...[]byte) error

func SendOk

func SendOk(w io.Writer, ht byte, body ...[]byte) error

func SendRecover added in v0.2.0

func SendRecover(w io.Writer, kid uint64, data ...[]byte) error

func Sign

func Sign(privateKey crypto.PrivateKey, data []byte) ([]byte, error)

func VerifySignature

func VerifySignature(pubKey crypto.PublicKey, data, sign []byte) bool

Types

type Conn

type Conn struct {
	net.Conn
	// contains filtered or unexported fields
}

func Dial

func Dial(network, addr string, rootCertFile string) (*Conn, error)

func DialWithCert

func DialWithCert(network, addr string, cert *x509.Certificate) (*Conn, error)

func DialWithCertAndKey

func DialWithCertAndKey(network, addr string, cert *x509.Certificate, clientKey any) (*Conn, error)

func (*Conn) Decrypt

func (c *Conn) Decrypt(ciphertext []byte) ([]byte, error)

func (*Conn) Encrypt

func (c *Conn) Encrypt(plaintext []byte) ([]byte, error)

func (*Conn) Read

func (c *Conn) Read(b []byte) (n int, err error)

func (*Conn) Recover added in v0.2.0

func (c *Conn) Recover(kid uint64, data []byte) error

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

func (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

func (*Conn) Write

func (c *Conn) Write(b []byte) (n int, err error)
type Header [HeaderLength]byte

func ReadHeader

func ReadHeader(r io.Reader) (*Header, error)

func (Header) ContentLength

func (h Header) ContentLength() uint32

func (Header) ReadBody

func (h Header) ReadBody(r io.Reader) ([]byte, error)

func (Header) Status

func (h Header) Status() byte

func (Header) Type

func (h Header) Type() byte

func (Header) Version

func (h Header) Version() byte

type HeaderStatus

type HeaderStatus = byte
const (
	HeaderStatusSuccess       HeaderStatus = 0
	HeaderStatusFail          HeaderStatus = 1
	HeaderStatusTimeout       HeaderStatus = 2
	HeaderStatusInvalidHeader HeaderStatus = 3
	HeaderStatusInvalidBody   HeaderStatus = 4
)

type HeaderType

type HeaderType = byte
const (
	HeaderTypeCryptoHandshake HeaderType = iota + 1
	HeaderTypeCryptoRecover
	HeaderTypeCryptoData
)

type Listener

type Listener struct {
	net.Listener
	// contains filtered or unexported fields
}

func (*Listener) Accept

func (l *Listener) Accept() (net.Conn, error)

type Packet

type Packet struct {
	Header
	Body []byte
}

func NewPacket

func NewPacket(ht, status byte, body ...[]byte) (*Packet, error)

func (Packet) Data

func (p Packet) Data() []byte

func (Packet) Send

func (p Packet) Send(w io.Writer) error

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(certFile, keyFile string) (*Server, error)

func NewServerFromMem

func NewServerFromMem(cert *x509.Certificate, key []byte) (*Server, error)

func (*Server) Listen

func (s *Server) Listen(network, addr string) (net.Listener, error)

type SessionState added in v0.2.0

type SessionState struct {
	Key         []byte
	ClientNonce [NonceLength]byte
	ServerNonce [NonceLength]byte
	ExpiresAt   time.Time
}

Directories

Path Synopsis
example
client command
server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL