HTTPS证书配置

做过 Android HTTPS 抓包的开发者和爬虫工程师一定都懂:Android 7.0 是一道分水岭
在这之前,手动安装一个用户 CA 证书就能轻松捕获 HTTPS 流量;但从 7.0 开始,绝大多数应用默认不再信任用户安装的证书,抓包工具只能看到满屏的连接重置和报错。

这篇文章就是为你准备的“全流程保姆级教程”。我们会从零开始,用 OpenSSL 生成自签名 CA 证书,将它转换成 Android 系统要求的特殊命名格式,再推送到 /system/etc/security/cacerts/ 目录(需要 ROOT 权限)。最后,还会提供一个一键 Python 脚本,把整个流程自动化,真正解放双手。


证书安装到系统分区(ROOT 版,最稳定)

Android 将证书分成两类:

  1. 用户证书:手动安装即可,但 Android 7.0+ 的应用默认不信任。
  2. 系统证书:位于 /system/etc/security/cacerts/,所有应用无条件信任——这就是我们要达成的目标。

操作前的准备:

  • 设备已获取 ROOT 权限。
  • 电脑安装了 OpenSSL 并正确添加到 PATH 环境变量。
  • 设备开启了 USB 调试,并且能用 adb 正常连接。

1. 在电脑上生成自签名 CA 证书

先在本地创建一根我们自己的“信任根证书”和私钥:

# 创建工作目录并进入
mkdir -p mobile_https_cert && cd mobile_https_cert

# 生成 2048 位 RSA 私钥(不带密码,方便代理工具自动加载)
openssl genrsa -out ca.key 2048

# 生成自签名 CA 证书(有效期 365 天,主题信息可按需修改)
openssl req -new -x509 -key ca.key -out ca.crt -days 365 -nodes \
  -subj "/CN=MobileCrawler CA/O=MyApp/O=MobileDev/C=CN"

2. 转换为 Android 系统 CA 需要的命名格式

Android 系统证书的命名规则很“Geek”:必须使用证书主题的旧版 MD5 哈希值,加上后缀 .0
用 OpenSSL 可以一键得到这个哈希值并完成重命名:

# 提取旧版 MD5 哈希
CERT_HASH=$(openssl x509 -inform PEM -subject_hash_old -in ca.crt | head -1)

# 重命名证书
mv ca.crt ${CERT_HASH}.0

3. 推送到系统分区并生效

接下来通过 adb 和 ROOT 权限,将证书放入只读的 system 分区:

# 重启 adb 进入 ROOT 模式
adb root

# 重新挂载 system 为可读写(部分设备挂载路径不同,但大多数通用 /system)
adb remount

# 推送重命名后的证书
adb push ${CERT_HASH}.0 /system/etc/security/cacerts/

# 设置正确的文件权限(644,系统 CA 的默认权限)
adb shell chmod 644 /system/etc/security/cacerts/${CERT_HASH}.0

# 重启设备让证书生效
adb reboot

4. 验证安装结果

设备重启后,打开终端快速检查一下:

adb shell ls -la /system/etc/security/cacerts/ | grep ${CERT_HASH}

如果能看到刚刚推送的 .0 文件,就说明系统证书安装已经成功了!


Python 一键证书管理脚本

为了避免每次都要手动敲一大串命令,我封装了一个轻量级的 Python 脚本。它集成了证书生成、格式转换、推送安装和验证的全流程,并带有友好的错误提示。

完整脚本

import os
import subprocess
import shutil
from typing import Optional, Tuple


class AndroidCertManager:
    """Android HTTPS 抓包/爬虫 CA 证书管理器"""

    def __init__(self, work_dir: str = "mobile_cert"):
        self.work_dir = work_dir
        self.ca_key: Optional[str] = None
        self.ca_crt: Optional[str] = None
        self.android_crt: Optional[str] = None
        self.cert_hash: Optional[str] = None
        os.makedirs(self.work_dir, exist_ok=True)

    def generate_ca(self) -> Tuple[Optional[str], Optional[str]]:
        """生成自签名 CA 证书和私钥"""
        try:
            key_path = os.path.join(self.work_dir, "ca.key")
            crt_path = os.path.join(self.work_dir, "ca.crt")

            # OpenSSL 生成私钥
            subprocess.run(
                ["openssl", "genrsa", "-out", key_path, "2048"],
                capture_output=True,
                check=True,
                timeout=30
            )

            # OpenSSL 生成自签名证书
            subprocess.run(
                [
                    "openssl", "req", "-new", "-x509",
                    "-key", key_path,
                    "-out", crt_path,
                    "-days", "365", "-nodes",
                    "-subj", "/CN=MobileCrawler CA/O=MobileDev/C=CN"
                ],
                capture_output=True,
                check=True,
                timeout=30
            )

            self.ca_key, self.ca_crt = key_path, crt_path
            print(f"✅ CA 生成成功!")
            print(f"  私钥:{self.ca_key}")
            print(f"  证书:{self.ca_crt}")
            return self.ca_key, self.ca_crt

        except subprocess.CalledProcessError as e:
            print(f"❌ OpenSSL 执行失败:{e.stderr.decode('utf-8', errors='ignore')}")
            return None, None
        except FileNotFoundError:
            print("❌ 未找到 OpenSSL,请先安装并加入 PATH")
            return None, None

    def convert_to_android(self) -> Optional[str]:
        """转换为 Android 系统 CA 格式"""
        if not self.ca_crt:
            print("❌ 请先生成 CA 证书!")
            return None

        try:
            # 提取旧版 MD5 哈希
            hash_result = subprocess.run(
                ["openssl", "x509", "-inform", "PEM", "-subject_hash_old", "-in", self.ca_crt],
                capture_output=True,
                check=True,
                text=True,
                timeout=10
            )
            self.cert_hash = hash_result.stdout.strip().split("\n")[0]

            # 重命名证书
            android_crt_path = os.path.join(self.work_dir, f"{self.cert_hash}.0")
            shutil.copy2(self.ca_crt, android_crt_path)
            self.android_crt = android_crt_path

            print(f"✅ Android 格式转换成功!")
            print(f"  文件:{self.android_crt}")
            return self.android_crt

        except subprocess.CalledProcessError as e:
            print(f"❌ 哈希提取失败:{e.stderr}")
            return None, None

    def install_to_system(self) -> bool:
        """推送并安装到 Android 系统分区(需 ROOT)"""
        if not self.android_crt:
            print("❌ 请先转换 Android 格式证书!")
            return False

        try:
            # 检查 adb 连接
            conn_result = subprocess.run(
                ["adb", "devices"],
                capture_output=True,
                text=True,
                timeout=10
            )
            if "device" not in conn_result.stdout.split("\n")[1]:
                print("❌ 未找到正常连接的 Android 设备!")
                return False

            # ROOT 模式准备
            print("🔐 正在尝试进入 adb ROOT 模式...")
            subprocess.run(["adb", "root"], capture_output=True, timeout=10)
            subprocess.run(["adb", "wait-for-device"], timeout=10)

            # 挂载 system 为可读写
            print("📂 正在重新挂载 system 为可读写...")
            remount_result = subprocess.run(
                ["adb", "remount"],
                capture_output=True,
                text=True,
                timeout=10
            )
            if "remount succeeded" not in remount_result.stdout:
                print(f"⚠️  挂载可能失败,请手动执行:adb shell su -c 'mount -o rw,remount /system'")

            # 推送证书
            print("📡 正在推送证书到设备...")
            remote_temp = f"/sdcard/{os.path.basename(self.android_crt)}"
            subprocess.run(
                ["adb", "push", self.android_crt, remote_temp],
                capture_output=True,
                check=True,
                timeout=10
            )

            # 移动到 system 分区并设置权限
            print("🔧 正在安装到系统证书目录...")
            remote_system = f"/system/etc/security/cacerts/{os.path.basename(self.android_crt)}"
            install_cmd = (
                f"su -c 'cp {remote_temp} {remote_system} && "
                f"chmod 644 {remote_system} && "
                f"rm {remote_temp}'"
            )
            subprocess.run(
                ["adb", "shell", install_cmd],
                capture_output=True,
                check=True,
                timeout=10
            )

            # 验证安装
            print("✅ 验证安装中...")
            check_cmd = f"ls -la /system/etc/security/cacerts/ | grep {self.cert_hash}"
            check_result = subprocess.run(
                ["adb", "shell", check_cmd],
                capture_output=True,
                text=True,
                timeout=10
            )
            if self.cert_hash in check_result.stdout:
                print("🎉 系统证书安装成功!请重启设备生效!")
                return True
            else:
                print("❌ 证书验证失败!")
                return False

        except subprocess.CalledProcessError as e:
            print(f"❌ 执行失败:{e.stderr.decode('utf-8', errors='ignore')}")
            return False
        except Exception as e:
            print(f"❌ 未知错误:{e}")
            return False

    def setup_full(self) -> bool:
        """一键完成生成-转换-安装全流程"""
        print("🚀 开始一键配置 Android HTTPS 证书...")
        if not self.generate_ca():
            return False
        if not self.convert_to_android():
            return False
        return self.install_to_system()


if __name__ == "__main__":
    # 直接运行一键配置
    manager = AndroidCertManager()
    manager.setup_full()

脚本使用方法

  1. 确保你的电脑已经装好 OpenSSL 和 adb,并且手机已 ROOT、USB 调试已打开。
  2. 将上面的脚本保存为 cert_manager.py
  3. 在终端运行:
    python cert_manager.py
  4. 脚本执行完毕后,手动重启设备即可。

别忘了后续配置

证书安装到系统分区只是第一步,还需要让代理工具用上我们自建的 CA:

  • 在代理工具(Fiddler / Charles / mitmproxy)中,加载刚才生成的 ca.keyca.crt,让它信任我们自己的 CA。
  • 在手机上设置 Wi‑Fi 代理,指向电脑的 IP 和代理工具监听的端口。
  • 搞定!现在就可以正常抓取 HTTPS 流量了。

提示:如果代理工具不支持直接加载自定义 CA,也可以将 ca.crt 导入到系统信任存储中使用。