本文目录导读:

这是一个非常专业的技术问题,首先要明确一点:在PHP项目中,"Handshake"(握手)与DNS(域名系统)的结合通常指的是 TLS/SSL 握手过程,而非指某个名为“Handshake”的特定PHP库或协议(虽然有Handshake这个去中心化域名项目,但一般不与常规PHP项目直接交集)。
为了给PHP开发者最有价值的答案,我假设你是在搭建加密的PHP应用(如HTTPS API、安全WebSocket)时,关心PHP如何完成与DNS相关的TLS握手,以下是核心要点和实现方式:
核心概念:TLS握手与DNS的关系
当PHP客户端(如 curl,file_get_contents)访问 https://example.com 时,流程是:
- DNS解析:解析
example.com的A/AAAA记录,获取IP地址。 - TCP连接:与IP的443端口建立TCP连接。
- TLS握手:这是关键,在此过程中,客户端需要验证服务器证书是否与域名匹配(即
example.com)。- PHP在此阶段会使用系统的证书信任存储(CA bundle)来验证服务器端出示的证书。
- 此验证过程依赖于DNS:因为客户端在验证证书中的
subjectAltName或commonName时,会对比连接时使用的域名(正是第一步DNS解析得到的域名)。注意:PHP本身在TLS握手时通常不主动发起额外的DNS请求(除非你要求OCSP stapling或证书透明度验证,这一步由底层OpenSSL或GnuTLS处理,它们可能使用DNS或HTTP获取OCSP响应)。
PHP项目中的典型场景实现
场景A:PHP作为HTTP客户端(出站请求)
使用 cURL 扩展时,需要正确配置来保证安全的TLS握手:
$ch = curl_init('https://api.example.com');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 验证服务器证书(必开)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 验证证书中的域名匹配(严格检查)
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem'); // 指定CA证书包(推荐)
// 或者使用系统默认路径(需根据OS设置)
// curl_setopt($ch, CURLOPT_CAPATH, '/etc/ssl/certs');
$result = curl_exec($ch);
- 关键点:
CURLOPT_SSL_VERIFYHOST=2确保了TLS握手时,服务器提供的证书必须与请求的DNS域名一致,这本质上是将DNS解析结果与证书绑定。
场景B:PHP作为HTTPS服务器(入站请求)
如果PHP运行在web服务器(如Nginx/Apache)后面,TLS握手在web服务器层完成(Nginx接受HTTPS、处理证书),PHP本身不直接参与TLS握手,但可以通过 $_SERVER['HTTPS'] 等变量感知,如果你用PHP内置服务器(开发用)自签证书,则需要自行管理证书和域名一致性。
php -S 0.0.0.0:443 -t /var/www \
-c /path/to/php.ini \
--php-ini /path/to/another.ini \
# 内置服务器不支持直接TLS,通常用代理
- 企业级做法:使用Nginx/Apache+Caddy处理TLS,PHP只通过FastCGI接收请求。
特殊高级场景:DNS-over-HTTPS(DoH) 与 PHP
如果你的PHP应用需要自己的TLS握手先经过加密DNS查询(防止DNS劫持或泄露),则需要修改系统的DNS解析链或使用特定库:
- 操作系统层配置(推荐,对PHP透明)。
- 在服务器上配置
systemd-resolved或stubby为DoH上游,PHP的gethostbyname()或cURL的默认DNS解析会自动走加密通道。
- 在服务器上配置
- 在PHP代码中使用特定库(如
DnsOverHttps)。- 使用
guzzlehttp/guzzle结合dns-over-httpsmiddleware,手动用HTTPS查询DNS后再建立cURL连接。 sockets扩展直接发送DoH请求(简单但繁琐)。
- 使用
示例:手动使用cURL + DNS预解析(绕过系统DNS)
$dns = ['example.com' => '93.184.216.34']; // 从外部DoH获取
$ip = $dns['example.com'];
$ch = curl_init("https://$ip/");
curl_setopt($ch, CURLOPT_RESOLVE, ["example.com:443:$ip"]); // 手动绑定域名到IP
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 仍验证证书中的域名
curl_setopt($ch, CURLOPT_SNI_ENABLED, true); // 发送SNI扩展(必须,否则CDN拒绝)
$result = curl_exec($ch);
注意:手动指定IP后,TLS握手时的SNI字段仍使用 example.com,证书验证也基于此,实现了自定义DNS解析 + 安全TLS握手,这是手写“Handshake”的典型实现。
潜在问题与最佳实践
- 证书域名不匹配:PHP报错
SSL certificate problem: unable to get local issuer certificate通常是因为CA包缺失(非DNS问题),如果是hostname mismatch,意味着URL域名与证书中的DNS name不一致。 - DNSSEC:PHP本身不验证DNSSEC,如果你需要防止DNS欺骗导致的TLS握手劫持,请在操作系统层面使用
unbound等递归解析器。 - 性能:每次TLS握手都包含DNS查询(除非缓存),建议:
curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 120);控制DNS缓存时间;或使用persistent connections(如curl_multi复用连接)。
- Handshake = TLS握手,在PHP中由底层OpenSSL/cURL控制。
- DNS 提供域名解析,用于TLS证书域名验证(CURLOPT_SSL_VERIFYHOST)和SNI扩展。
- 安全做法:保证
CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST开启;多数情况下无需改动DNS处理,除非你需要DoH防DNS劫持。 - 若你确实指Handshake(去中心化域名项目),现有PHP生态与之集成较少,通常仍需通过传统DNS(如
.bit域名需额外解析器),TLS验证逻辑不变,但证书管理可能由Handshake的TLA(顶级域)体系额外控制。
如果你有更具体的错误或代码场景(如报错信息、使用了特定的Handshake库),欢迎补充细节,我可以进一步帮忙排查。