作者:lingulingo 更新时间:2026
项目仓库:github.com/lingulingo/tlsprint
很多网站在你请求时会判断"你是不是真人浏览器"。这个判断往往不只是看 User-Agent,而是看你连 TLS 握手都发了什么——你的浏览器在 ClientHello 里带的密码套件顺序、扩展顺序、支持的曲线、签名算法,以及后面 HTTP/2 的 SETTINGS 顺序、WINDOW_UPDATE、伪头/请求头顺序,每一家浏览器都不一样,这就是"TLS 指纹"。
业界用 JA3 / JA4 给它打指纹。给个真实例子:
如果你用 Go 默认的 net/http 发起请求,它的 TLS 指纹和浏览器完全不一样,很多站点看一眼就判定为"不是浏览器",直接给你 403 或跳到验证页。
而且大部分库的"HTTP/2 指纹"都没做——只把 TLS 做精确,HTTP/2 却走 Go 默认实现,两个加起来其实还是对不上真实浏览器的完整行为。
所以我写了 tlsprint,一个引擎无关的 Go TLS 指纹库:
1. 查预设 + 算指纹
2. 直接拨号(自己写 HTTP)
3. 作为 HTTP 客户端(POST JSON 自动反序列化)
client 依赖我 fork 的 tlsprint/http2(基于 golang.org/x/net/http2)。我给它加了一个 Fingerprint 配置,把预设里的 HTTP/2 指纹直接写进连接序言和请求帧:
实测:服务端上报的 akamai_fingerprint 是:
和 Chrome 152 抓包完全一致;请求头顺序也和抓包逐字节一致。(有断言测试覆盖。)
我是这么验证"它能干活"的——用 chrome-152 + 一份新鲜的会话 cookie 请求一个被 Akamai 保护的电商页:
拿到真实商品页,不是验证页。这说明"连接层 + 指纹 + cookie"整条链路是通的。
tlsprint 想做的,是把"指纹"这件事做得标准、纯 Go、且真正的双向字节精确,把"数据层"和"引擎"解耦,让任何 Go 项目都能轻松接入。它不依赖 CGO、根模块零依赖、API 顺手,还带 47 个可校验的真实预设和一遍完整的真实站点验证。
如果你觉得有用,欢迎去 GitHub 点个 star ⭐、提 Issue 或 PR(新增指纹预设、更好的 HTTP/2 保真度、文档都可以):
声明:本文仅用于 TLS 协议研究与反爬对抗测试,请只对你有权测试的系统使用。项目为 MIT 协议,http2 子模块派生自 golang.org/x/net/http2(BSD-3-Clause,遵循上游)。
| 方案 |
做法 |
痛点 |
| uTLS |
自己造的 TLS 栈,能模仿浏览器 |
只有引擎,其他全要你自己写;指纹定义绑死在代码里 |
| tls-client / go-bypasser |
基于 uTLS 的 HTTP 客户端 |
依赖整个引擎;HTTP/2 层仍是 Go 默认,不逐字节 |
| curl_cffi / pyhttpx |
C 语言 libcurl 封装 |
需要 CGO/原生库,跨平台编译头大 |
| proteus |
高层客户端 + 大预设 |
依赖 CGO + python 生成;数据层字符串型、丑陋 |
# Chrome 152 的 JA3(前两段就是密码套件 & 扩展顺序)
771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,
45-17613-13-43-10-0-51764-18-51-35-27-11-5-65281-16-65037-23,4588-29-23-24,0
# Akamai 的 HTTP/2 指纹
1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
go mod init myapp
go get github.com/lingulingo/tlsprint/client@v1.0.0
package main
import (
"fmt"
tlsclient "github.com/lingulingo/tlsprint/client"
)
func main() {
resp, err := tlsclient.Get("ed8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6@1L8s2y4Q4x3X3g2H3k6h3g2@1i4K6u0W2N6%4y4Q4x3V1k6S2M7r3W2Q4x3V1k6S2L8r3H3`.",
tlsclient.Impersonate("chrome-152"),
tlsclient.Query("q", "tls fingerprint"),
tlsclient.Header("X-Api-Key", "secret"),
)
if err != nil {
panic(err)
}
fmt.Println(resp.StatusCode(), resp.Proto())
fmt.Println(resp.String())
}
p := preset.MustBuiltin().Lookup("chrome-152")
fmt.Println(p.Key, p.Product, p.Version, p.UserAgent())
s := ja3.FromProfile(&p.TLS)
fmt.Println(s, ja3.Hash(s))
conn, err := tlsutls.DialByName(ctx, "tcp", "example.com:443", "chrome-152", nil)
defer conn.Close()
var result map[string]any
resp, err := tlsclient.Post("f91K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5N6s2c8H3j5X3W2F1i4K6u0W2L8%4u0Y4i4K6u0r3M7r3!0K6N6l9`.`.",
tlsclient.Impersonate("chrome-152"),
tlsclient.Body(map[string]any{"name": "tlsprint"}),
tlsclient.Result(&result),
)
fmt.Println(result)
Settings = []Setting{{HeaderTableSize, 65536}, {EnablePush, 0}, {InitialWindowSize, 6291456}, {MaxHeaderListSize, 262144}}
WindowUpdate = 15663105
PseudoHeaderOrder = "m,a,s,p"
HeaderOrder = [sec-ch-ua, sec-ch-ua-mobile, ..., priority]
Priority = {Exclusive:true, Weight:255}
1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于 3天前
被灵谷编辑
,原因: