stream_socket_enable_crypto() 报错 code 1 的直接原因是 OpenSSL 握手时收不到服务端证书链,常见于自签名证书、CA 文件缺失或流上下文未配对;非阻塞模式下必失败,需先切阻塞再启用加密。

stream_socket_enable_crypto() 报错 code 1 的直接原因
根本不是证书“不对”,而是 OpenSSL 在握手阶段收不到服务端的证书链——常见于本地开发用自签名证书、CA 文件缺失、或 PHP 流上下文没配对。错误信息里 ssl3_get_server_certificate:certificate verify failed 是典型表现,说明客户端发完 Client HELLO 后,等不到服务端回的证书就超时失败了。
非阻塞模式下 stream_socket_enable_crypto() 必然失败
PHP 的 stream_socket_enable_crypto() 在非阻塞 socket 上几乎必报 code 1。它内部依赖同步 I/O 完成 TLS 握手,而非阻塞模式下数据未就绪就立刻返回,函数判为失败。
- 临时切回阻塞模式再启用加密:
stream_set_blocking($fd, true)→stream_socket_enable_crypto($fd, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)→stream_set_blocking($fd, false) - 别在
stream_socket_client("tls://...")后再调这个函数——tls://协议已自动完成握手,重复调用会冲突 - 用
STREAM_CRYPTO_METHOD_TLS_CLIENT(客户端)还是STREAM_CRYPTO_METHOD_TLS_SERVER(服务端),必须和角色严格匹配;混用会导致握手消息错位
file_get_contents() 或 stream_context_create() 走 HTTPS 时的证书陷阱
很多人以为只配 verify_peer => false 就能过,其实漏掉了关键点:即使关验证,PHP 仍可能因底层 OpenSSL 版本太旧、不支持服务端用的 TLS 版本而卡在握手第一步。
- 强制指定 TLS 1.2:
"crypto_method" => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT -
cafile路径必须真实存在且可读,Windows 下路径用正斜杠或双反斜杠,不能用单反斜杠 - 若用自签名证书,
allow_self_signed => true必须配合verify_peer => false,单独设前者无效 - PHP 7.2+ 中
STREAM_CRYPTO_METHOD_TLS_CLIENT等价于TLS_ANY,但某些旧 OpenSSL 库会退化到 TLS 1.0,建议显式锁定版本
cURL 和 stream 函数的 SSL 验证逻辑不互通
你在 php.ini 里设了 curl.cainfo,不代表 file_get_contents() 或 stream_socket_client() 会自动读它。这两个体系用的是完全独立的证书配置路径。
立即学习“PHP免费学习笔记(深入)”;
-
file_get_contents()只认stream_context_create()里传的cafile或系统默认路径(可通过openssl_get_cert_locations()查) -
curl_setopt($ch, CURLOPT_CAINFO, ...)的路径必须绝对、可读,且文件是 PEM 格式(含完整证书链,不是单个 crt) - 禁用验证仅限调试:必须同时关
CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST,少一个都可能报错
最常被忽略的是:错误看起来像证书问题,实际是 OpenSSL 库本身不支持服务端协商的密码套件,或者 PHP 编译时链接的 OpenSSL 版本太老——这种情况下,换证书、改配置都没用,得升级 PHP 或系统 OpenSSL。



















