在Android安全领域,APK签名信息就像应用的“身份证”,无论是检测恶意篡改、追溯开发者身份,还是批量验证应用合法性,都离不开它。但多数人依赖的apksigner
、keytool
等工具,在跨平台集成、自动化批量处理场景下总有诸多限制。今天就带大家用C++从零实现APK签名提取,彻底掌握这一核心技术。
一、先搞懂:APK签名藏在哪?
很多人不知道,APK本质是个标准ZIP压缩包,签名信息并非藏在复杂的二进制结构里,而是集中在META-INF/
目录下,主要以三种文件形式存在:
.RSA
:最常见的签名文件,存储基于RSA算法的签名数据.DSA
:基于DSA算法的签名文件,多见于早期应用.EC
:基于椭圆曲线加密(ECC)的签名文件,轻量化场景常用
这些文件内部都遵循PKCS#7标准格式,把证书链、签名算法、公钥等关键信息打包存储。我们要做的,就是从APK中“挖”出这些文件,再解析出里面的核心数据。
二、核心工具:为什么选libzip+OpenSSL?
实现签名提取需要两个关键库,各自分工明确:
- libzip:轻量级跨平台ZIP解析库,能高效打开APK文件、遍历目录条目,精准定位
META-INF/
下的签名文件,避免自己写复杂的ZIP解压逻辑 - OpenSSL:业界标准的加密库,自带PKCS#7解析、X.509证书处理功能,能轻松提取证书主体、指纹、公钥等信息,省去手动解析加密格式的麻烦
这两个库都支持Windows、Linux、Android多平台,编译后体积小,非常适合嵌入到安全分析工具、自动化脚本中。
三、手把手实现:3步提取签名信息
第一步:用libzip定位并读取签名文件
先通过libzip打开APK,遍历所有文件条目,筛选出META-INF/
目录下的签名文件,再读取其二进制数据:
#include <libzip/zip.h>
#include <vector>
#include <string>
#include <iostream>// 从APK中提取签名文件二进制数据
std::vector<uint8_t> get_signature_from_apk(const std::string& apk_path) {// 1. 打开APK文件int zip_err = 0;zip_t* apk_zip = zip_open(apk_path.c_str(), ZIP_RDONLY, &zip_err);if (!apk_zip) {std::cerr << "APK打开失败!错误码:" << zip_err << std::endl;return {};}std::vector<uint8_t> sig_data;// 2. 遍历APK内所有文件条目zip_int64_t entry_count = zip_get_num_entries(apk_zip, 0);for (zip_int64_t i = 0; i < entry_count; ++i) {// 获取文件名称const char* entry_name = zip_get_name(apk_zip, i, 0);if (!entry_name) continue;std::string filename = entry_name;// 3. 筛选签名文件:META-INF目录下,后缀为.RSA/.DSA/.ECif (filename.find("META-INF/") == 0 && (filename.ends_with(".RSA") || filename.ends_with(".DSA") || filename.ends_with(".EC"))) {// 获取文件大小zip_stat_t entry_stat;if (zip_stat_index(apk_zip, i, 0, &entry_stat) != 0) continue;// 读取文件内容到内存zip_file_t* sig_file = zip_fopen_index(apk_zip, i, 0);if (!sig_file) continue;sig_data.resize(entry_stat.size);zip_int64_t read_len = zip_fread(sig_file, sig_data.data(), entry_stat.size);if (read_len != entry_stat.size) {sig_data.clear();}zip_fclose(sig_file);break; // 通常一个APK只有一个签名文件,找到后直接退出循环}}// 关闭APK文件zip_close(apk_zip);return sig_data;
}
第二步:用OpenSSL解析PKCS#7结构
拿到签名文件的二进制数据后,用OpenSSL解析PKCS#7格式,提取证书链中的关键信息(主体、颁发者、SHA256指纹等):
#include <openssl/pkcs7.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>// 解析签名数据,输出证书信息
void parse_signature_data(const std::vector<uint8_t>& sig_data) {if (sig_data.empty()) {std::cerr << "签名数据为空!" << std::endl;return;}// 1. 初始化内存BIO,加载签名数据BIO* mem_bio = BIO_new(BIO_s_mem());BIO_write(mem_bio, sig_data.data(), sig_data.size());// 2. 解析PKCS#7结构PKCS7* pkcs7 = d2i_PKCS7_bio(mem_bio, nullptr);BIO_free(mem_bio);if (!pkcs7) {std::cerr << "PKCS#7解析失败!" << std::endl;ERR_print_errors_fp(stderr);return;}// 3. 检查是否为签名类型(排除其他PKCS#7用途)if (!PKCS7_type_is_signed(pkcs7)) {std::cerr << "非签名类型的PKCS#7数据!" << std::endl;PKCS7_free(pkcs7);return;}// 4. 提取证书链STACK_OF(X509)* cert_chain = pkcs7->d.sign->cert;if (!cert_chain || sk_X509_num(cert_chain) == 0) {std::cerr << "证书链为空!" << std::endl;PKCS7_free(pkcs7);return;}// 5. 遍历证书链,输出每个证书的关键信息for (int i = 0; i < sk_X509_num(cert_chain); ++i) {X509* cert = sk_X509_value(cert_chain, i);std::cout << "=== 证书 " << (i + 1) << " ===" << std::endl;// 5.1 证书主体(开发者/机构信息)char subject[512] = {0};X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));std::cout << "主体:" << subject << std::endl;// 5.2 证书颁发者(CA机构信息)char issuer[512] = {0};X509_NAME_oneline(X509_get_issuer_name(cert), issuer, sizeof(issuer));std::cout << "颁发者:" << issuer << std::endl;// 5.3 证书序列号ASN1_INTEGER* serial = X509_get_serialNumber(cert);BIGNUM* bn_serial = ASN1_INTEGER_to_BN(serial, nullptr);char* serial_hex = BN_bn2hex(bn_serial);std::cout << "序列号:" << serial_hex << std::endl;OPENSSL_free(serial_hex);BN_free(bn_serial);// 5.4 SHA256指纹(应用身份唯一标识)unsigned char sha256_fingerprint[EVP_MAX_MD_SIZE] = {0};unsigned int fp_len = 0;if (X509_digest(cert, EVP_sha256(), sha256_fingerprint, &fp_len)) {std::cout << "SHA256指纹:";for (unsigned int j = 0; j < fp_len; ++j) {printf("%02X", sha256_fingerprint[j]);if (j < fp_len - 1) printf(":");}std::cout << std::endl;}// 5.5 签名算法const X509_ALGOR* sig_alg = X509_get0_signature(nullptr, nullptr, cert);int alg_nid = OBJ_obj2nid(sig_alg->algorithm);std::cout << "签名算法:" << OBJ_nid2ln(alg_nid) << std::endl;}// 释放资源PKCS7_free(pkcs7);
}
第三步:整合调用,测试效果
把两个核心函数整合,传入APK路径即可提取签名信息:
int main(int argc, char* argv[]) {if (argc != 2) {std::cout << "用法:" << argv[0] << " <APK文件路径>" << std::endl;return 1;}// 1. 提取签名文件数据std::vector<uint8_t> sig_data = get_signature_from_apk(argv[1]);if (sig_data.empty()) {std::cerr << "未提取到签名数据!" << std::endl;return 1;}// 2. 解析签名数据并输出parse_signature_data(sig_data);return 0;
}
四、关键提醒:保护你的签名提取工具
如果你把这套代码做成安全分析工具、批量检测脚本,一定要注意自身安全——核心解析逻辑很容易被逆向破解,导致工具被篡改或滥用。
这里推荐用Virbox Protector对工具进行加固,它能提供多层保护:
- 代码虚拟化:把核心的签名解析函数转换成虚拟机指令,逆向难度呈指数级提升
- 反调试/反注入:阻止攻击者用调试器跟踪代码、注入恶意代码篡改逻辑
- 资源加密:对编译后的二进制文件加密,防止被静态分析
- 内存保护:防止运行时内存中的关键数据被dump或修改
加固后,工具的抗逆向能力会大幅提升,确保你的技术方案不被轻易破解。
五、为什么推荐这套方案?
相比传统工具,C+++libzip+OpenSSL的方案有三个核心优势:
- 跨平台:一次编写,可在Windows、Linux、macOS甚至嵌入式设备上编译运行
- 高集成度:能直接嵌入到安全引擎、自动化测试平台中,无需单独调用外部工具
- 灵活可控:可根据需求自定义提取字段(比如只需要SHA256指纹),减少冗余计算
无论是做APK批量检测、安全合规审查,还是开发自定义的Android工具,这套方案都能满足需求,让你彻底摆脱对系统工具的依赖。