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

咨询在Golang中获取请求的TLS指纹JA3及计算JA3S的方法

在Golang中获取完整ClientHelloInfo并计算JA3/JA3S指纹

获取包含Extensions的ClientHelloInfo

Go标准库的tls.ClientHelloInfo在GetCertificate回调中不会填充Extensions字段,因为该回调触发时机较早。要获取完整的ClientHello数据(含Extensions),推荐以下两种方案:

方案1:使用Go 1.20+的ClientHelloCallback

Go 1.20新增的tls.Config.ClientHelloCallback回调会在ClientHello阶段触发,此时传入的*tls.ClientHelloInfo已包含完整的Extensions字段。示例代码:

package main

import (
	"crypto/tls"
	"log"
	"net/http"
)

func main() {
	addr := ":443"
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("OK"))
	})

	// 加载服务器证书
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		log.Fatalf("加载证书失败: %v", err)
	}

	srv := &http.Server{
		Addr:    addr,
		Handler: handler,
		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
			ClientHelloCallback: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
				// 此处chi.Extensions已填充完整的客户端扩展信息
				log.Printf("客户端扩展类型: %v", chi.Extensions)
				// 可在此处调用JA3计算逻辑
				return nil, nil // 返回nil将使用默认证书
			},
		},
	}

	log.Fatal(srv.ListenAndServeTLS("", ""))
}

方案2:低版本Go手动解析原始ClientHello报文

如果使用Go 1.20以下版本,需要自定义TLS监听器,手动读取并解析ClientHello的原始字节流来提取Extensions。核心思路是在握手前拦截连接,解析TLS记录层和ClientHello报文:

package main

import (
	"crypto/tls"
	"log"
	"net"
)

type customTLSListener struct {
	tls.Listener
}

func (l *customTLSListener) Accept() (net.Conn, error) {
	conn, err := l.Listener.Accept()
	if err != nil {
		return nil, err
	}

	tlsConn := conn.(*tls.Conn)
	// 触发握手以获取完整连接状态
	if err := tlsConn.Handshake(); err != nil {
		return nil, err
	}

	// 从连接状态中获取ClientHello的原始数据(需自行解析)
	state := tlsConn.ConnectionState()
	// 此处需要根据TLS协议规范解析state.ClientHello的字节流,提取扩展字段
	// 解析逻辑可参考TLS 1.2/1.3协议文档中ClientHello的结构定义

	return tlsConn, nil
}

func main() {
	listener, err := tls.Listen("tcp", ":443", &tls.Config{
		Certificates: loadCertificates(),
	})
	if err != nil {
		log.Fatal(err)
	}

	customListener := &customTLSListener{Listener: listener}
	// 后续使用customListener处理连接
}

func loadCertificates() []tls.Certificate {
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		log.Fatal(err)
	}
	return []tls.Certificate{cert}
}

计算JA3和JA3S指纹

JA3(客户端TLS指纹)计算

JA3基于ClientHello中的5个字段生成MD5哈希:TLS版本、加密套件列表、扩展类型列表、椭圆曲线列表、椭圆曲线点格式列表。核心实现代码:

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"sort"
	"strings"
	"crypto/tls"
)

func ComputeJA3(chi *tls.ClientHelloInfo) string {
	// 提取TLS版本
	version := fmt.Sprintf("%d", chi.Version)

	// 提取加密套件(按ClientHello顺序)
	var cipherSuiteStrs []string
	for _, cs := range chi.CipherSuites {
		cipherSuiteStrs = append(cipherSuiteStrs, fmt.Sprintf("%d", cs))
	}
	cipherStr := strings.Join(cipherSuiteStrs, "-")

	// 提取扩展类型(排序后)
	var extIDs []string
	for id := range chi.Extensions {
		extIDs = append(extIDs, fmt.Sprintf("%d", id))
	}
	sort.Strings(extIDs)
	extStr := strings.Join(extIDs, "-")

	// 提取椭圆曲线列表
	var curveStrs []string
	for _, c := range chi.SupportedCurves {
		curveStrs = append(curveStrs, fmt.Sprintf("%d", c))
	}
	curveStr := strings.Join(curveStrs, "-")

	// 提取椭圆曲线点格式
	var pointFormatStrs []string
	for _, pf := range chi.SupportedPoints {
		pointFormatStrs = append(pointFormatStrs, fmt.Sprintf("%d", pf))
	}
	pfStr := strings.Join(pointFormatStrs, "-")

	// 拼接JA3原始字符串
	ja3Raw := fmt.Sprintf("%s,%s,%s,%s,%s", version, cipherStr, extStr, curveStr, pfStr)

	// 生成MD5哈希
	hash := md5.Sum([]byte(ja3Raw))
	return hex.EncodeToString(hash[:])
}

JA3S(服务器TLS指纹)计算

JA3S基于ServerHello中的3个字段生成MD5哈希:TLS版本、选中的加密套件、服务器返回的扩展列表。可通过HandshakeComplete回调获取服务器握手状态:

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"sort"
	"strings"
	"crypto/tls"
)

func setupServer() *http.Server {
	cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
	return &http.Server{
		Addr: ":443",
		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
			HandshakeComplete: func(state tls.ConnectionState) {
				// 提取服务器TLS版本
				version := fmt.Sprintf("%d", state.Version)
				// 提取选中的加密套件
				cipher := fmt.Sprintf("%d", state.CipherSuite)
				// 提取服务器扩展类型(排序后)
				var extIDs []string
				for _, ext := range state.ServerHelloExtensions {
					extIDs = append(extIDs, fmt.Sprintf("%d", ext.Id))
				}
				sort.Strings(extIDs)
				extStr := strings.Join(extIDs, "-")

				// 拼接JA3S原始字符串
				ja3sRaw := fmt.Sprintf("%s,%s,%s", version, cipher, extStr)
				// 生成MD5哈希
				hash := md5.Sum([]byte(ja3sRaw))
				ja3s := hex.EncodeToString(hash[:])
				fmt.Printf("JA3S指纹: %s\n", ja3s)
			},
		},
	}
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:10:06