HTTPS certificate configuration

Developers and crawler engineers who have done Android HTTPS packet capture must understand: Android 7.0 is a watershed. Before this, HTTPS traffic could be easily captured by manually installing a user CA certificate; but starting from 7.0, most applications no longer trust user-installed certificates by default, and packet capture tools can only see a full screen of connection resets and error reports.

This article is a "full-process nanny-level tutorial" prepared for you. We will start from scratch, use OpenSSL to generate a self-signed CA certificate, convert it into the special naming format required by the Android system, and then push it to/system/etc/security/cacerts/Directory (requires ROOT permissions). Finally, a one-click Python script will be provided to automate the entire process and truly free your hands.


Install the certificate to the system partition (ROOT version, the most stable)

Android divides certificates into two categories:

  1. User Certificate: You can install it manually, but Android 7.0+ applications are not trusted by default.
  2. System Certificate: located in/system/etc/security/cacerts/, Unconditional Trust for all applications - this is what we want to achieve.

Preparation before operation:

  • The device has obtained ROOT permissions.
  • The computer has OpenSSL installed and correctly added to the PATH environment variable.
  • The device has USB debugging turned on and it worksadbConnect normally.

1. Generate a self-signed CA certificate on your computer

First create our own "trust root certificate" and private key locally:

# 创建工作目录并进入
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. Convert to the naming format required by Android system CA

The naming convention for Android system certificates is very "geek": You must use the legacy MD5 hash of the certificate subject, plus a suffix.0
Using OpenSSL, you can get this hash value and complete the rename with one click:

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

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

3. Push to the system partition and take effect

Next, put the certificate into the read-only system partition with adb and ROOT permissions:

# 重启 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. Verify installation results

After the device restarts, open a terminal and do a quick check:

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

If you can see what you just pushed.0file, it means that the system certificate installation has been successful!


Python one-click certificate management script

In order to avoid having to type a long list of commands manually every time, I encapsulated a lightweight Python script. It integrates the entire process of certificate generation, format conversion, push installation and verification, and comes with friendly error prompts.

Complete script

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()

How to use the script

  1. Make sure your computer has OpenSSL and adb installed, your phone is ROOT, and USB debugging is turned on.
  2. Save the above script ascert_manager.py
  3. Run in terminal:
    python cert_manager.py
  4. After the script is executed, manually restart the device.

Don’t forget the subsequent configuration

Installing the certificate to the system partition is only the first step. We also need to let the proxy tool use our self-built CA:

  • In the proxy tool (Fiddler / Charles / mitmproxy), ** load the just generatedca.keyandca.crt** to make it trust our own CA.
  • Set up a Wi‑Fi proxy on your phone, pointing to your computer’s IP and the port the proxy tool listens on.
  • Done! Now you can crawl HTTPS traffic normally.

Tip: If the proxy tool does not support direct loading of custom CAs, you can alsoca.crtImported into the system trust store for use.