HTTPS证书配置

搞过Android HTTPS抓包的开发者/爬虫er都懂,Android 7.0是个分水岭式的小坑——之前手动装个用户CA证书全搞定,之后99%的应用默认不信任用户凭证,抓包直接“红叉叉/HTTPS连接重置”。

今天这篇就是专门解决这个问题的保姆级干货:从OpenSSL生成自签名CA、转Android系统要求的哈希命名格式,到推送到/system/etc/security/cacerts/(需要ROOT权限),最后给你封装好一键Python管理脚本,完全解放双手!


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

Android把证书分为两类:

  1. 用户证书:手动装就行,但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系统CA文件名必须用「证书主题的旧版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为可读写(注意:部分设备分区挂载路径不同,比如用/vendor,但大多数通用/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检查目录:

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

如果输出了刚才的.0文件,说明安装成功!


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设备);
  2. 复制脚本保存为cert_manager.py
  3. 直接运行:
    python cert_manager.py
  4. 脚本完成后手动重启设备即可。

后续配置提醒

证书生效后,记得:

  1. 配置代理工具(Fiddler/Charles/Mitmproxy)使用我们刚才生成的ca.crtca.key
  2. 在设备上设置代理指向电脑的IP和代理工具端口;
  3. 就可以正常抓取HTTPS流量啦!