macOS 上 Charles 代理 HTTPS 抓包失败问题完整解决方案

一、问题现象
在 macOS 上使用 Charles 进行 HTTPS 抓包时,经常遇到以下情况:
- 浏览器或终端中的 HTTPS 请求返回错误,无法正常加载页面。
curl命令报错:curl: (60) SSL certificate problem: self signed certificate in certificate chain。- Charles 中请求显示
unknown或无法解密 HTTPS 流量。
根本原因是:Charles 作为中间人代理,会用自己的自签名根证书来动态签发目标服务器的证书。但操作系统和应用程序(如 curl)默认不信任这个自签名证书,从而导致 SSL 握手失败。
二、基础环境准备
1. 设置 Charles 代理端口
Charles 默认使用 8888 端口作为 HTTP/HTTPS 代理端口。请确认:
- 菜单栏
Proxy->Proxy Settings->HTTP Proxy端口为8888。 - 勾选
Enable transparent HTTP proxying(可选)。
2. 配置终端代理环境变量
在终端中执行以下命令,让 curl 等命令行工具通过 Charles 代理发出请求:
bash
export http_proxy="http://127.0.0.1:8888"
export https_proxy="http://127.0.0.1:8888"
export HTTP_PROXY="http://127.0.0.1:8888"
export HTTPS_PROXY="http://127.0.0.1:8888"
注意:大小写都设置是为了兼容不同程序对环境变量的读取习惯。可以将其写入
~/.zshrc并用proxy_on函数封装,方便开关。
3. 验证代理连通性
先测试代理是否能正常连接:
bash
curl -vv https://www.baidu.com
如果出现 HTTP/1.1 200 Connection established 以及后续的 SSL 证书错误,说明代理通道已建立,但证书信任问题尚未解决。
三、临时解决方案(每次请求指定证书)
如果不希望修改系统证书信任设置,或者只想临时测试,可以使用 curl 的 --cacert 参数直接指定 Charles 的根证书文件。
步骤 1:导出 Charles 根证书并转换为 PEM 格式
- 在 Charles 菜单栏点击
Help->SSL Proxying->Export Charles Root Certificate and Private Key...。 - 在弹出的窗口中设置密码(例如
123456),选择保存位置,文件格式默认为.p12。 - 使用 OpenSSL 将
.p12转换为curl能识别的.pem格式:
bash
openssl pkcs12 -in /path/to/charles-ssl-proxying.p12 -out /path/to/charles.pem -nodes
-in:输入的 .p12 文件路径-out:输出的 .pem 文件路径(建议放在桌面或用户目录)-nodes:不加密私钥,避免 curl 需要交互输入密码
执行后会要求输入导出时设置的密码,输入后回车即可。
步骤 2:使用 --cacert 发起请求
bash
curl -k --cacert /Users/mac/Downloads/charles.pem -vv https://www.baidu.com
-k或--insecure:跳过对目标服务器证书的验证(因为 Charles 签发的证书与百度原始证书不同,但我们已经信任 Charles 根证书,所以可以不加-k,仅用--cacert即可)。实际上,为了完整验证,推荐只使用--cacert:
bash
curl --cacert /Users/mac/Downloads/charles.pem -vv https://www.baidu.com
此时应该能正常返回 HTML 内容,Charles 中也能看到解密的 HTTPS 请求详情。
缺点 :每次 curl 都需要手动添加 --cacert 参数,比较繁琐。
四、永久解决方案(系统级信任 Charles 根证书)
让 macOS 系统以及所有命令行工具(curl、git、brew 等)默认信任 Charles 的根证书,一劳永逸。
核心思路
- 将 Charles 根证书导入 系统钥匙串 (
/Library/Keychains/System.keychain)。 - 明确设置该证书为 始终信任(SSL 信任)。
详细步骤
1. 删除旧的 Charles 证书
打开"钥匙串访问"(Keychain Access),分别在 登录 和 系统 钥匙串中搜索 Charles,删除所有相关证书(例如 Charles Proxy CA)。这可以避免新旧证书冲突。
2. 重新安装 Charles 根证书
- 在 Charles 菜单栏点击
Help->SSL Proxying->Install Charles Root Certificate。 - 此时证书会被安装到 登录 钥匙串(默认位置)。
3. 将证书移动到系统钥匙串并设置为始终信任
- 打开"钥匙串访问",在 登录 钥匙串中找到刚才安装的
Charles Proxy CA...证书。 - 将证书拖拽 到左侧的 系统 钥匙串中(需要输入管理员密码)。
- 切换到 系统 钥匙串,双击该证书打开详情窗口。
- 展开 信任 部分,将 "使用此证书时" 下拉菜单改为 "始终信任"。
- 关闭窗口,再次输入密码确认修改。
4. 重启 Charles 和终端
完全退出 Charles(Cmd+Q),关闭所有终端窗口,重新打开。
5. 测试验证
在终端直接运行:
bash
curl -vv https://www.baidu.com
不再需要 -k 或 --cacert,应该能够正常返回页面内容,同时 Charles 中显示完整的 HTTPS 请求和解密后的数据。
五、常见问题与排查
Q1:永久方案设置后 curl 仍然报 SSL 错误
- 确认证书是否在 系统 钥匙串中,而不是 登录。
- 确认信任设置中的 "SSL" 或 "使用此证书时" 确实为 "始终信任"。
- 尝试在 Charles 中执行
Help->SSL Proxying->Reset SSL Certificates,然后重启 Charles。 - 重启 Mac 以清理所有缓存。
Q2:浏览器可以抓包,但终端不行
- 检查终端环境变量
https_proxy是否正确设置。 - 有些终端配置文件(如
.zshrc)中可能覆盖了代理变量,可以临时用unset https_proxy清除后再测试。 - 确认 Charles 的
macOS Proxy选项已勾选(Proxy->macOS Proxy),这会自动为系统设置代理。
Q3:Charles 中某些请求仍然显示 unknown
- 确认
SSL Proxying已开启且 Include 规则包含*:443或具体域名。 - 可能是客户端启用了 QUIC (HTTP/3) 协议,Charles 无法代理 UDP 流量。解决方法:在 Chrome 中访问
chrome://flags/#enable-quic将其禁用,或强制客户端使用 HTTP/1.1/2。 - 可能是客户端使用了 SSL Pinning(证书锁定),如银行 App、部分企业软件。这种情况下 Charles 无法直接解密,需要更高级的 Hook 工具(如 Frida)。
Q4:导出 .p12 时忘记密码怎么办?
- 无法恢复密码,只能重新在 Charles 中导出新证书,设置一个你记得住的密码。
六、总结
| 方案 | 操作复杂度 | 持久性 | 适用场景 |
|---|---|---|---|
临时方案 (--cacert) |
低(需每次加参数) | 临时 | 偶尔调试,不想修改系统设置 |
| 永久方案(系统钥匙串+信任) | 中(一次配置) | 永久 | 长期开发、频繁抓包 |
推荐开发者在自己的 Mac 上采用永久方案,配置一次后,所有命令行工具和应用程序都能无缝通过 Charles 抓包 HTTPS 流量,极大提升效率。
如果在实践中遇到其他问题,欢迎根据具体错误信息进一步排查。Happy debugging!