Android 数字签名
Android 应用的数字签名是系统用以验证应用来源和完整性的关键机制。每个 APK 文件在发布前都必须由开发者使用私钥进行签名,系统在安装或更新时会校验签名是否与已有版本一致,从而保证应用包未被篡改,并确保其来自同一开发者。签名证书中包含的主体(Subject)、颁发者(Issuer)、序列号(Serial Number)、有效期(Validity Period)以及指纹信息(SHA1、SHA256)构成了判断应用身份的基础数据。
在应用安全检测、威胁分析、合规审查及自动化构建流程中,往往需要直接获取 APK 的签名信息,以验证证书的合法性或比对来源。此类信息通常用于确认应用是否由可信开发者签发,或判断其是否被重新打包、修改或伪造。数字签名的提取与解析因此成为应用安全分析的重要组成部分。
常见的签名查看方式主要依赖 Android SDK 工具,例如使用 apksigner verify --print-certs your.apk
查看证书摘要,或使用 keytool -list -printcert -jarfile your.apk
输出签名证书信息。这些工具能够满足常规需求,但在嵌入式环境、安全扫描引擎、持续集成系统或跨平台安全框架中,命令行调用方式往往难以直接集成,执行效率和可扩展性也受到限制。
为实现更高的灵活性与自动化能力,签名提取可通过代码在本地完成。APK 文件本质上是一个 ZIP 压缩包,其签名数据通常存储在 META-INF/
目录下的 .RSA
、.DSA
或 .EC
文件中。通过 libzip
读取签名文件,再结合 OpenSSL
对内部的 PKCS#7 结构进行解析,即可提取完整的证书链信息,包括签名算法、公钥长度、证书有效期和指纹摘要等。
本篇文章将介绍如何使用 C++ 结合 libzip
与 OpenSSL
,实现对 Android APK 数字签名的手动提取与解析。整个过程不依赖外部工具,可直接嵌入到安全分析系统或自动化平台中,为应用签名验证与证书信息识别提供可靠的编程实现方式。
APK 签名结构与官方工具
Android APK 文件的数字签名机制依赖于 Java 签名体系,通常被称为 JAR 签名(V1 签名)。APK 文件本质上是一个 ZIP 压缩包,其中的签名文件存储在 META-INF/
目录下,常见的文件类型包括 .RSA
、.DSA
和 .EC
。这些文件内部采用 PKCS#7 结构保存签名数据和证书链,证书中包含应用发布者的主体信息、颁发者信息、序列号、有效期、公钥以及指纹摘要等。通过解析这些签名文件,可以恢复完整的证书链,从而获得签名的关键属性。
系统在安装或更新 APK 时,会验证签名的完整性和有效性。安装程序会检查应用包内容是否被修改,以及签名证书是否与设备上已有版本一致。签名验证的过程不仅判断文件是否被篡改,还能保证应用来源的连续性和唯一性。在安全分析或自动化构建环境中,直接获取签名文件并解析其内容可以完成类似验证功能,同时提供更多可编程的分析接口。
官方工具提供了便捷的方式来查看 APK 的签名信息。apksigner
是 Android SDK 中推荐的命令行工具,其命令 apksigner verify --print-certs <apk文件>
可以输出签名证书的摘要、SHA-1 和 SHA-256 指纹以及证书有效期等关键信息。另一种方法是使用 Java 自带的 keytool
工具,通过命令 keytool -list -printcert -jarfile <apk文件>
或对解压后的签名文件执行 keytool -printcert -file <RSA文件>
,同样可以获取签名证书详情。
尽管这些工具在日常开发和测试中可满足大多数需求,但它们依赖于 Java 运行环境或 Android SDK,难以直接嵌入原生安全分析系统或跨平台自动化流程中。为了实现更高效、灵活和可扩展的签名信息获取,需要通过编程方式直接读取 APK 内的签名文件并解析 PKCS#7 数据结构,从而获取完整的证书信息和指纹数据。
从 APK 中提取签名文件的编程实现
APK 文件本质上是 ZIP 压缩包,签名数据通常存储在 META-INF/
目录下的 .RSA
、.DSA
或 .EC
文件中。为了在程序中提取签名信息,需要首先读取这些文件的二进制内容,以供后续解析 PKCS#7 结构。
实现提取的核心步骤包括:打开 APK 文件、遍历压缩包中的文件条目、识别签名文件并读取其内容。C++ 结合 libzip
库可以完成这些操作,从而在内存中获取签名文件的完整数据。
以下代码展示了读取 APK 中签名文件的实现思路:
std::vector<uint8_t> extract_signature_from_apk(const std::string& apk_path) {
int err = 0;
zip_t* zip = zip_open(apk_path.c_str(), 0, &err);
if (!zip) {
std::cerr << "Failed to open APK: " << apk_path << std::endl;
return {};
}
std::vector<uint8_t> signature_data;
zip_int64_t num_entries = zip_get_num_entries(zip, 0);
for (zip_int64_t i = 0; i < num_entries; i++) {
const char* name = zip_get_name(zip, i, 0);
if (!name) continue;
std::string filename(name);
if (filename.find("META-INF/") == 0 &&
(filename.find(".RSA") != std::string::npos ||
filename.find(".DSA") != std::string::npos ||
filename.find(".EC") != std::string::npos)) {
zip_stat_t st;
if (zip_stat_index(zip, i, 0, &st) == 0) {
zip_file_t* file = zip_fopen_index(zip, i, 0);
if (file) {
signature_data.resize(st.size);
zip_int64_t bytes_read = zip_fread(file, signature_data.data(), st.size);
if (bytes_read != static_cast<zip_int64_t>(st.size))
signature_data.clear();
zip_fclose(file);
break;
}
}
}
}
zip_close(zip);
return signature_data;
}
上述实现过程的关键点包括:
zip_open
用于打开 APK 压缩包,返回对应的文件指针;- 遍历所有文件条目,通过
zip_get_name
获取文件名,判断是否位于META-INF/
并具有.RSA
、.DSA
或.EC
后缀,从而识别签名文件; - 使用
zip_stat_index
获取文件大小,再通过zip_fopen_index
和zip_fread
将文件内容读取到内存向量中; - 读取完成后关闭文件和压缩包,返回包含签名数据的字节数组。
通过这一方法,可以在程序中以二进制形式获取 APK 的签名文件,为后续利用 OpenSSL
解析 PKCS#7 结构并提取证书信息提供输入数据。这种方式具有平台独立性,无需依赖外部命令行工具,可直接嵌入安全分析系统或自动化流程中,实现高效的签名信息获取。
解析签名数据并提取证书信息
在获取 APK 内签名文件的二进制数据后,需要解析其 PKCS#7 结构,以提取包含的证书链及关键信息。数字签名文件通常为 .RSA
、.DSA
或 .EC
格式,内部采用 PKCS#7 封装标准存储签名和证书数据。C++ 可以结合 OpenSSL 库对内存中的签名数据进行解析,从而获取证书的主体、颁发者、有效期、签名算法、公钥信息以及指纹摘要。
以下代码片段展示了基本的解析流程:
void analyze_signature_data(const std::vector<uint8_t>& signature_data) {
if (signature_data.empty()) return;
BIO* bio = BIO_new(BIO_s_mem());
BIO_write(bio, signature_data.data(), signature_data.size());
PKCS7* p7 = d2i_PKCS7_bio(bio, nullptr);
BIO_free(bio);
if (!p7) {
std::cerr << "Failed to parse PKCS7 signature" << std::endl;
ERR_print_errors_fp(stderr);
return;
}
if (PKCS7_type_is_signed(p7)) {
STACK_OF(X509)* certs = p7->d.sign->cert;
for (int i = 0; i < sk_X509_num(certs); i++) {
X509* cert = sk_X509_value(certs, i);
char subject[256], issuer[256];
X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));
X509_NAME_oneline(X509_get_issuer_name(cert), issuer, sizeof(issuer));
std::cout << "--- Certificate " << i + 1 << " ---\n";
std::cout << "Subject: " << subject << "\n";
std::cout << "Issuer: " << issuer << "\n";
ASN1_INTEGER* serial = X509_get_serialNumber(cert);
BIGNUM* bn = ASN1_INTEGER_to_BN(serial, nullptr);
char* serial_hex = BN_bn2hex(bn);
std::cout << "Serial Number: " << serial_hex << "\n";
OPENSSL_free(serial_hex);
BN_free(bn);
const X509_ALGOR* sig_alg;
X509_get0_signature(nullptr, &sig_alg, cert);
int nid = OBJ_obj2nid(sig_alg->algorithm);
std::cout << "Signature Algorithm: " << OBJ_nid2ln(nid) << "\n";
EVP_PKEY* pkey = X509_get_pubkey(cert);
if (pkey) {
std::cout << "Public Key Type: " << OBJ_nid2ln(EVP_PKEY_id(pkey)) << "\n";
if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA) {
RSA* rsa = EVP_PKEY_get1_RSA(pkey);
if (rsa) {
std::cout << "Key Size: " << RSA_size(rsa) * 8 << " bits\n";
RSA_free(rsa);
}
}
EVP_PKEY_free(pkey);
}
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len;
if (X509_digest(cert, EVP_sha1(), md, &md_len)) {
std::cout << "SHA1 Fingerprint: ";
for (unsigned int j = 0; j < md_len; j++) {
printf("%02X", md[j]);
if (j < md_len - 1) printf(":");
}
std::cout << "\n";
}
if (X509_digest(cert, EVP_sha256(), md, &md_len)) {
std::cout << "SHA256 Fingerprint: ";
for (unsigned int j = 0; j < md_len; j++) {
printf("%02X", md[j]);
if (j < md_len - 1) printf(":");
}
std::cout << "\n";
}
}
}
PKCS7_free(p7);
}
该实现过程的核心点包括:
- 使用
BIO_new
和BIO_write
将签名数据加载到内存 BIO 中; - 通过
d2i_PKCS7_bio
将内存数据解析为 PKCS#7 结构; - 判断 PKCS#7 是否为签名类型,通过
p7->d.sign->cert
获取证书链; - 遍历证书链,依次读取证书主体、颁发者、序列号、签名算法和公钥信息;
- 使用
X509_digest
计算 SHA1 和 SHA256 指纹,以便进行唯一标识和比对。
通过此方法,可以在程序中获取 APK 的完整签名证书信息,为进一步的安全分析、签名验证或证书比对提供数据基础。整个流程无需依赖外部工具,可直接集成到 C++ 安全分析模块或自动化构建系统中,实现高效、可编程的签名提取功能。
在对 APK 文件进行结构分析和签名提取后,还应该考虑程序可能面临的安全威胁。未经加固的应用和库文件可能被逆向分析或篡改,从而暴露核心逻辑和敏感数据。为应对这些风险,可以使用专业的加固工具对程序进行保护。Virbox Protector 提供包括代码混淆、指令级虚拟化、调试检测和异常运行环境防护在内的多层保护措施,在保持程序正常运行的前提下,显著增加逆向分析难度,提升软件整体安全性。
总结
本文系统地阐述了从 Android APK 文件中获取数字签名信息的编程实现方法。首先明确了 APK 数字签名在应用安全体系中的作用,包括验证应用来源、确保完整性及支持安全分析的必要性。随后分析了 APK 文件的签名结构,说明签名文件通常存储于 META-INF/
目录下的 .RSA
、.DSA
或 .EC
文件中,并采用 PKCS#7 结构封装证书链和签名数据。
在技术实现方面,介绍了利用 C++ 结合 libzip
读取 APK 内签名文件的二进制内容的方法,并展示了如何使用 OpenSSL 解析 PKCS#7 结构,从而提取证书主体、颁发者、序列号、有效期、签名算法、公钥信息及 SHA 指纹等关键数据。这一方法无需依赖外部工具,可在本地完成签名信息提取,并为自动化分析和安全验证提供可靠的数据支持。
通过以上步骤,可以实现对 APK 文件签名的完整解析,为安全分析和证书验证提供可编程、跨平台的技术基础。