咨询在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
相关产品推荐
相关产品推荐

