- 网络
- 网页爬虫
- 后端
【免费下载链接】curl_cffi
Python binding for curl-impersonate fork via cffi. A http client that can impersonate browser tls/ja3/http2 fingerprints.
本篇技术指南聚焦于curl_cffi浏览器模拟能力中一个关键且容易被忽视的细节——TLSpre_shared_key(PSK,扩展编号 41)扩展。文章从 RFC 8446 的协议定义出发,解释 PSK 扩展在真实浏览器访问中的出现规律、它与 HTTP Session Cookie 的机制类比,以及在使用旋转代理时可能引发的 IP 关联风险;随后结合仓库源码,深入讲解curl_cffi 0.12.0引入的proxy_credential_no_reuse选项如何将 TLS 会话缓存与代理用户名、出口 IP 绑定。读完本文,你将理解 PSK 扩展为何不能由客户端强行注入、curl_cffi 为什么建议让客户端自动管理该扩展,并掌握在高并发代理抓取场景下规避 TLS 会话泄漏的配置思路。
什么是 TLS PSK(41) 扩展
PSK 是Pre-Shared Key(预共享密钥)的缩写,其语义定义在 RFC 8446(TLS 1.3)第 2.2 节:
Once a handshake has completed, the server can send the client a PSK identity that corresponds to a unique key derived from the initial handshake (see Section 4.6.1). The client can then use that PSK identity in future handshakes to negotiate the use of the associated PSK.
简单来说:当一次 TLS 握手完成后,服务器可以向客户端下发一个与该连接派生的密钥对应的 PSK 身份标识(identity);客户端在后续握手时携带该身份,即可协商复用之前建立的会话密钥,从而跳过完整的密钥交换过程,实现会话恢复(session resumption)。
在 IANA 的 TLS 扩展类型表中,该扩展的编号为41,即pre_shared_key,与之配套的还有编号 45 的psk_key_exchange_modes(PSK 密钥交换模式)以及编号 42 的early_data(0-RTT 早期数据)。curl_cffi在 curl_cffi/requests/impersonate.py 中维护了完整的 TLS 扩展名映射表,其中明确列出了:
41: "pre_shared_key", 42: "early_data", 45: "psk_key_exchange_modes",同时在 TLS_CIPHER_NAME_MAP 中也能看到 PSK 相关的密码套件,例如:
0x008C: "TLS_PSK_WITH_AES_128_CBC_SHA", 0x008D: "TLS_PSK_WITH_AES_256_CBC_SHA", 0xC035: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xC036: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xCCAC: "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",PSK 扩展的出现规律:首次访问没有,二次访问出现
PSK 扩展不是每次握手的固定成员,它的出现取决于客户端是否持有可复用的会话密钥。典型规律如下:
- 首次访问一个网站时,客户端没有可用的 PSK,因此在 ClientHello 的扩展列表中不会出现
pre_shared_key; - 短时间内再次访问同一个网站时,客户端可以基于服务器之前下发的 PSK 身份发起会话恢复,此时扩展列表中就会携带 PSK。
你可以通过访问https://tls.peet.ws/api/all亲身体验这一行为:第一次打开时观察返回的 TLS 指纹 JSON,扩展列表中通常没有 PSK;随后刷新页面,pre_shared_key扩展就会出现在列表中。这是验证 PSK 行为最直接、无需额外工具的观测方法。
要正确实现 PSK 扩展,客户端必须在内存中或磁盘上持久化某种形式的会话缓存(session cache)。所有主流浏览器在很早之前就内置了这一能力,这是它们"看起来像真实浏览器"的众多指纹细节之一。
PSK 与 HTTP Session Cookie 的机制类比
文档给出了一个非常到位的类比:PSK 的机制与行为就像 HTTP 会话 Cookie。
| 维度 | HTTP Session Cookie | TLS PSK |
|---|---|---|
| 服务器下发 | 首次响应时下发 Set-Cookie | 首次握手后下发 PSK 身份标识 |
| 客户端回传 | 后续请求自动携带 Cookie | 后续握手自动携带 PSK 扩展 |
| 用途 | 恢复断开的 HTTP 会话 | 恢复断开的 TLS 会话 |
| 语义 | 证明"你之前来过" | 证明"你之前建立过连接" |
服务器在下发 PSK 时,有可能将来源 IP 与密钥的映射关系保存下来。这意味着 PSK 不是纯匿名的会话凭据——它隐式携带了"这个密钥是从哪个 IP 发起的握手中派生的"这一关联信息。
旋转代理场景下的隐患:IP 变了,PSK 没变
既然服务器可能维护 IP 与 PSK 的映射,那么在使用旋转代理(rotating proxies)时复用 TLS 会话就会引发问题。文档中给出了一个直观的时序图:
┌───────────┐ ┌───────────┐ │ │ │ │ │ │ IP: 10.0.0.1 │ │ │ ┼─────────────TLS─Hello──────────────► │ │ │ │ │ │ ◄─────────────PSK:─xxx───────────────┼ │ │ │ │ │ │ │ │ │ │ │ │ Server │ │ Client │ IP: 10.0.0.2 │ │ │ ┼─────────────TLS─with─PSK───────────► │ │ │ │ │ │ ◄─────────────Blocked────────────────┼ │ │ │ │ │ │ │ PSK: xxx was │ │ │ │ associated with │ │ │ │ 10.0.0.1, not │ │ └───────────┘ 10.0.0.2 └───────────┘整个流程可以拆解为三步:
- 客户端通过出口 IP
10.0.0.1(例如某个代理节点)完成首次握手,服务器下发了 PSKxxx并记录该密钥与10.0.0.1的关联; - 客户端切换代理,出口 IP 变为
10.0.0.2,但 TLS 会话缓存仍然有效,于是它带着 PSKxxx发起握手; - 服务器发现该 PSK 关联的源 IP 是
10.0.0.1而非10.0.0.2,于是判定异常并直接阻断连接。
这就是"TLS 会话与旋转代理冲突"的核心风险:会话恢复本是降低握手开销的优化机制,但在代理轮换场景下,它反而成了暴露客户端行为异常的信号。
proxy_credential_no_reuse:将会话缓存绑定到代理凭据
幸运的是,curl_cffi从0.12.0版本起新增了名为proxy_credential_no_reuse的选项。启用后,TLS 会话缓存将基于代理用户名和 IP 进行绑定:只有当代理用户名与出口 IP 都匹配时,会话才允许被复用。
从服务器的视角看,效果是:Pre-Shared Key被锁定在与它最初建立握手相同的源 IP 上,不会再在不同的出口节点之间"跳跃"。
在源码层面,该选项的实现位置清晰可查:
- 选项本身定义在 curl_cffi/const.py:
PROXY_CREDENTIAL_NO_REUSE = 0 + 1022,属于CurlOpt枚举; - 在 curl_cffi/requests/utils.py 的代理配置段中,只要检测到
proxies配置非空,就会自动启用该选项:
if proxies: # Turn on proxy_credential_no_reuse, which has the following benefits: # 1. New connection will be made when proxy username changed # 2. New TLS session will be created based on proxy address, i.e. when accessing # the same site with different proxies, TLS session won't leak previous IP. c.setopt(CurlOpt.PROXY_CREDENTIAL_NO_REUSE, 1)这段代码注释清晰地说明了启用后的两个直接收益:
- 代理用户名变化时建立新连接——不再复用旧的底层连接;
- 基于代理地址创建新的 TLS 会话——使用不同代理访问同一站点时,TLS 会话不会泄漏之前的 IP 关联信息。
这意味着在使用requests.Session配合代理访问时,你无需手动干预 PSK 行为,curl_cffi会自动完成会话缓存与代理凭据的绑定。文档同时提到,未来版本可能在启用代理时默认开启该选项,这也符合抓取与反检测场景的安全默认值取向。
我应该如何启用 PSK 扩展?
结论是:你不需要(也不应该)手动启用它。请先阅读上面的机制解释——一般来说,客户端应自行管理该扩展,并在第二次请求时自动提供它。
以下几点是文档给出的明确建议:
- 不要强行注入随机 PSK:从服务器角度看,如果你强行携带一个随机值的 PSK 扩展,就像携带了一个无效 Cookie 值,是"你不是真实访客"的明显信号。真实浏览器绝不会发送一个毫无来源的 PSK。
- 不要试图完全禁用 PSK:如果你出于"伪装成首次访客"的目的而希望不发送 PSK 扩展,当前版本不支持这一行为。可选的替代方案是回退到更早版本的
curl_cffi,或者在每次请求时创建新的 Session(新的 Session 自然没有可复用的会话缓存,也就不会携带 PSK)。 - 让客户端决定:值得注意的是,一些同样面向浏览器模拟的 HTTP 客户端会暴露"添加或不添加 PSK"的控制开关,但如果你的目标是模拟真实浏览器,应当让客户端自己决定何时发送 PSK,而不是手动干预。
小结
TLS PSK(41) 扩展是浏览器 TLS 指纹中一个"低频但关键"的细节:
- 它由客户端会话缓存驱动,首次访问不出现、二次访问自动出现,是会话恢复机制的体现;
- 它与 HTTP Session Cookie 机制同构,但服务器可能维护 IP 与密钥的映射,因此旋转代理 + 复用 TLS 会话 = 被识别的风险;
curl_cffi自0.11.0(libcurl 8.13.0)起在底层支持 PSK 扩展,自0.12.0起通过proxy_credential_no_reuse选项将 TLS 会话缓存与代理用户名、出口 IP 绑定,规避会话在代理节点间漂移的问题;- 正确姿势是不手动注入、不强行禁用,让客户端基于真实会话状态自动管理该扩展。
相关实现与文档可以在仓库的 curl_cffi/requests/utils.py、curl_cffi/requests/impersonate.py、curl_cffi/const.py 以及 docs/impersonate/psk.rst 中进一步查阅。
- 网络
- 网页爬虫
- 后端
【免费下载链接】curl_cffi
Python binding for curl-impersonate fork via cffi. A http client that can impersonate browser tls/ja3/http2 fingerprints.
相关推荐
CANN ops-transformer 算子解析:aclnnMoeTokenPermuteGrad 接口详解与 MoE Token Permute 反向传播实现
CANN ops transformer 算子解析:aclnnMoeTokenPermuteGrad 接口详解与 MoE Token Permute 反向传播实
网络网页爬虫后端轻松下载B站视频:解锁4K大会员画质的完整指南
轻松下载B站视频:解锁4K大会员画质的完整指南 你是否遇到过这样的困扰:看到B站上精彩的教程视频想要离线学习,但网络不稳定无法流畅观看?或者身为大会员想要保存4
网络网页爬虫后端kube-state-metrics 中 VolumeAttachment 指标详解:从指标清单到源码实现
kube state metrics 中 VolumeAttachment 指标详解:从指标清单到源码实现 kube state metrics 通过内置的 v
网络网页爬虫后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考