Android Runtime代码签名与验证流程剖析
一、代码签名与验证概述
在Android系统中,代码签名与验证是保障应用程序安全运行的重要机制。通过对应用代码进行签名,并在安装、运行过程中进行严格验证,能够确保应用来源可信、代码未被篡改,防止恶意软件伪装成合法应用入侵系统。Android Runtime(ART)作为应用运行的核心环境,深度参与了代码签名与验证的全流程,从应用安装时的初步校验,到运行时的类加载检查,构建起多层防护体系。接下来,我们将从源码层面逐步解析这一复杂且关键的安全流程。
二、Android代码签名基础
2.1 签名原理与格式
Android应用的签名基于公钥密码学体系,开发者使用私钥对应用的二进制文件(通常是APK)进行签名,生成签名信息。用户设备则使用对应的公钥验证签名,以此确认代码的完整性和来源可信性。
签名文件存储在APK的META-INF目录下,常见的签名文件包括.RSA(包含签名数据)、.SF(包含文件摘要列表)等。在frameworks/base/cmds/signapk/目录下的signapk.jar文件,提供了签名生成的核心逻辑:
// frameworks/base/cmds/signapk/signapk.jar
public class SignApk {
// 使用私钥对APK进行签名
public static void sign(
PrivateKey privateKey, // 开发者私钥
X509Certificate[] certificateChain, // 证书链
File inputApk, // 待签名的APK文件
File outputApk) throws IOException, GeneralSecurityException {
// 解析APK文件内容
ZipFile zipInput = new ZipFile(inputApk);
ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(outputApk));
// 生成签名文件摘要
Manifest manifest = generateManifest(zipInput);
generateSignatureFile(manifest, privateKey);
// 将原始文件、签名文件写入新APK
copyFiles(zipInput, zipOutput);
addSignatureFiles(zipOutput, certificateChain, privateKey);
zipInput.close();
zipOutput.close();
}
}
上述代码中,generateManifest方法遍历APK内的所有文件,计算其摘要并生成MANIFEST.MF文件;generateSignatureFile使用私钥对MANIFEST.MF进行签名,生成.SF文件;最后通过addSignatureFiles将签名文件和证书链写入APK,完成签名过程。
2.2 数字证书体系
Android的签名验证依赖数字证书,证书包含了开发者身份信息、公钥以及证书颁发机构(CA)的签名。系统通过信任的根证书验证应用证书的合法性。
证书结构在org.apache.harmony.xnet.provider.jsse.X509CertificateImpl类(位于libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/目录)中定义:
// libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/X509CertificateImpl.java
public class X509CertificateImpl implements X509Certificate {
private final byte[] encoded; // 证书编码数据
private final Principal subjectDN; // 证书主体(开发者)
private final Principal issuerDN; // 证书颁发者
private final Date notBefore; // 证书生效时间
private final Date notAfter; // 证书过期时间
// 验证证书是否有效
@Override
public boolean isValid(Date date) {
return date.after(notBefore) && date.before(notAfter);
}
// 获取证书公钥
@Override
public PublicKey getPublicKey() {
// 从证书数据中解析公钥
return parsePublicKey(encoded);
}
}
系统在验证时,会检查证书是否在有效期内,并通过根证书验证证书链的可信性,确保签名来源可靠。
三、应用安装阶段的签名验证
3.1 PackageManagerService的初始校验
在应用安装过程中,PackageManagerService(PMS)负责核心的验证工作。PackageManagerService位于frameworks/base/services/core/java/com/android/server/pm/目录,其验证流程如下:
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public class PackageManagerService {
// 解析APK并验证签名
private PackageParser.Package parsePackage(
File packageFile, // APK文件路径
int flags) {
PackageParser parser = new PackageParser(packageFile);
PackageParser.Package pkg = parser.parsePackage(packageFile, flags);
// 验证APK签名
if (!verifySignatures(pkg)) {
throw new PackageManager.NameNotFoundException(
"Package has invalid signatures");
}
return pkg;
}
// 核心签名验证逻辑
private boolean verifySignatures(PackageParser.Package pkg) {
// 获取APK中的签名信息
Signature[] signatures = pkg.signatures;
if (signatures == null || signatures.length == 0) {
return false;
}
// 检查签名是否与系统信任的证书匹配
for (Signature signature : signatures) {
if (!isSignatureTrusted(signature)) {
return false;
}
}
return true;
}
}
parsePackage方法调用PackageParser解析APK文件,并通过verifySignatures验证签名。verifySignatures会对比APK中的签名与系统信任的证书列表,若不匹配则终止安装。
3.2 签名验证的细节处理
在验证过程中,系统会检查签名文件的完整性和一致性。PackageParser在解析APK时,会验证MANIFEST.MF、.SF和.RSA文件之间的关联:
// frameworks/base/core/java/android/content/pm/PackageParser.java
public class PackageParser {
// 解析APK中的签名信息
private void parseSignature(ZipFile zip) throws PackageParserException {
ZipEntry manifestEntry = zip.getEntry("META-INF/MANIFEST.MF");
ZipEntry sfEntry = zip.getEntry("META-INF/*.SF");
ZipEntry rsaEntry = zip.getEntry("META-INF/*.RSA");
// 验证MANIFEST.MF的完整性
if (!verifyManifest(manifestEntry)) {
throw new PackageParserException("Invalid MANIFEST.MF");
}
// 验证.SF文件与MANIFEST.MF的一致性
if (!verifySignatureFile(sfEntry, manifestEntry)) {
throw new PackageParserException("Invalid signature file");
}
// 验证.RSA文件的签名有效性
if (!verifySignatureBlock(rsaEntry, sfEntry)) {
throw new PackageParserException("Invalid signature block");
}
}
// 验证MANIFEST.MF文件
private boolean verifyManifest(ZipEntry entry) {
// 读取文件内容并计算摘要
byte[] data = readEntryData(entry);
byte[] calculatedDigest = calculateDigest(data);
// 对比文件中记录的摘要与计算结果
String storedDigest = getStoredDigest(entry);
return Arrays.equals(calculatedDigest, storedDigest);
}
}
通过逐层验证签名文件间的依赖关系,确保APK在签名后未被篡改。
四、ART类加载阶段的签名验证
4.1 ClassLinker的验证逻辑
当应用运行时,ART的ClassLinker负责类的加载与验证。在art/runtime/class_linker.cc中,类加载时会检查类文件的签名:
// art/runtime/class_linker.cc
ObjPtr<mirror::Class> ClassLinker::DefineClass(
Thread* self,
const char* descriptor,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
size_t class_def_idx) {
// 获取Dex文件的签名
const Signature& dexSignature = dex_file.GetSignature();
// 检查类加载器的签名与Dex文件是否匹配
if (!class_loader->HasMatchingSignature(dexSignature)) {
self->ThrowIllegalAccessError("Class signature mismatch");
return nullptr;
}
// 其他类定义操作
//...
return result;
}
DefineClass方法获取Dex文件的签名,并与加载该类的类加载器签名进行比对。若签名不匹配,将抛出异常并阻止类的加载。
4.2 签名验证与ClassLoader的协作
Android的ClassLoader在加载类时,也会参与签名验证。例如,PathClassLoader在frameworks/base/core/java/dalvik/system/目录下,其加载逻辑如下:
// frameworks/base/core/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(
String dexPath, // APK路径
ClassLoader parent) {
super(dexPath, null, null, parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 从Dex文件中加载类
Class<?> clazz = super.findClass(name);
// 验证类的签名
if (!verifyClassSignature(clazz)) {
throw new SecurityException("Class signature verification failed");
}
return clazz;
}
// 类签名验证方法
private boolean verifyClassSignature(Class<?> clazz) {
// 获取类所属的Dex文件签名
Signature dexSignature = getDexSignature(clazz);
// 获取当前ClassLoader的签名
Signature loaderSignature = getClassLoaderSignature();
// 对比签名
return dexSignature.equals(loaderSignature);
}
}
findClass方法在加载类后,调用verifyClassSignature验证类的签名,确保运行的代码来源可信。
五、动态代码加载的签名验证
5.1 JNI代码的签名检查
当应用通过JNI调用本地代码时,ART同样会验证代码的签名。在art/runtime/jni/jni_dlsym.cc中,加载本地库时的验证逻辑如下:
// art/runtime/jni/jni_dlsym.cc
void* JniDlOpen(const char* library_name, int flags) {
// 打开本地库文件
void* handle = dlopen(library_name, flags);
if (handle == nullptr) {
return nullptr;
}
// 验证库文件的签名
if (!VerifyLibrarySignature(library_name)) {
dlclose(handle);
return nullptr;
}
return handle;
}
// 验证本地库签名
bool VerifyLibrarySignature(const char* library_name) {
// 获取库文件的签名信息
Signature librarySignature = GetLibrarySignature(library_name);
// 获取应用的签名
Signature appSignature = GetAppSignature();
// 对比签名
return librarySignature.equals(appSignature);
}
JniDlOpen方法在打开本地库后,通过VerifyLibrarySignature检查库的签名是否与应用签名一致,不一致则拒绝加载。
5.2 插件化与热修复的签名验证
对于插件化和热修复场景,系统对动态加载的代码同样有严格的验证要求。以插件化为例,在加载插件APK时,PluginManager(假设存在)会执行类似应用安装的签名验证流程:
// 假设的PluginManager类
public class PluginManager {
// 加载插件APK
public Plugin loadPlugin(File pluginFile) {
// 解析插件APK
PackageParser.Package pkg = parsePackage(pluginFile);
// 验证插件签名
if (!verifySignatures(pkg)) {
throw new PluginLoadException("Plugin has invalid signatures");
}
// 加载插件中的类
PluginClassLoader classLoader = new PluginClassLoader(pkg, getBaseClassLoader());
return new Plugin(classLoader);
}
}
通过复用应用安装阶段的签名验证逻辑,确保动态加载的代码同样安全可信。
六、系统更新与签名验证
6.1 OTA更新的签名验证
在系统进行OTA(Over-The-Air)更新时,签名验证是关键环节。UpdateEngine(位于system/update_engine/目录)负责验证更新包的签名:
// system/update_engine/update_engine.cc
bool UpdateEngine::VerifyUpdatePackage() {
// 读取更新包中的签名信息
Signature updateSignature = GetUpdateSignature();
// 获取系统信任的证书
X509Certificate[] trustedCertificates = GetTrustedCertificates();
// 验证签名
for (X509Certificate cert : trustedCertificates) {
if (cert.verify(updateSignature)) {
return true;
}
}
return false;
}
VerifyUpdatePackage方法对比更新包签名与系统信任的证书,只有签名验证通过才能继续安装更新。
6.2 系统镜像签名验证
在设备启动过程中,Bootloader会验证系统镜像的签名。以aboot(位于bootable/bootloader/aboot/目录)为例:
// bootable/bootloader/aboot/aboot.c
int verify_system_image() {
// 读取系统镜像的签名
unsigned char* signature = read_image_signature();
// 获取设备的公钥
unsigned char* public_key = get_device_public_key();
// 验证签名
if (rsa_verify(signature, public_key)) {
return 0; // 验证通过
} else {
return -1; // 验证失败
}
}
verify_system_image方法使用设备的公钥验证系统镜像签名,确保启动的系统未经篡改。
七、签名验证的错误处理与回滚机制
7.1 验证失败的处理逻辑
当签名验证失败时,系统会根据场景采取不同的处理方式。在应用安装阶段,PackageManagerService会终止安装并提示用户:
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void handleSignatureVerificationFailure(
PackageParser.Package pkg,
String errorMessage) {
// 记录错误日志
Log.e(TAG, "Signature verification failed for " + pkg.packageName + ": " + errorMessage);
// 向用户显示错误提示
sendVerificationFailureNotification(pkg.packageName, errorMessage);
// 清理临时安装文件
cleanUpInstallationFiles(pkg);
}
在类加载或动态代码加载时,ART会抛出异常并阻止代码执行,防止恶意代码运行。
7.2 回滚与恢复机制
对于系统更新或应用安装失败的情况,系统支持回滚机制。在OTA更新中,UpdateEngine会保留旧版本系统,若验证失败则回滚:
// system/update_engine/update_engine.cc
void UpdateEngine::RollbackUpdate() {
// 恢复旧版本系统镜像
RestoreOldSystemImage();
// 清除临时更新文件
DeleteUpdateFiles();
// 重启设备
RebootDevice();
}
通过回滚机制,确保系统在签名验证失败时仍能保持稳定运行。
八、签名验证的性能优化
8.1 缓存与快速验证
为提升验证效率,系统会缓存已验证的签名信息。在PackageManagerService中,通过SignatureCache类实现缓存:
// frameworks/base/services/core/java/com/android/server/pm/SignatureCache.java
public class SignatureCache {
private final Map<String, Signature> cache = new HashMap<>(); // 缓存签名
// 获取缓存的签名
public Signature get(String packageName) {
return cache.get(packageName);
}
// 添加签名到缓存
public void put(String packageName, Signature signature) {
cache.put(packageName, signature);
}
}
在验证时,优先从缓存中获取签名,减少重复验证开销。
8.2 并行验证与优化算法
在处理多个文件或大型APK时,系统采用并行验证和优化算法。例如,在验证APK内多个文件的签名时,PackageParser可采用多线程验证:
// frameworks/base/core/java/android/content/pm/PackageParser.java
private void verifySignaturesParallel(ZipFile zip) {
int numFiles = zip.size();
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (int i = 0; i < numFiles; i++) {
ZipEntry entry = zip.entries().nextElement();
executor.submit(() -> {
try {
// 独立验证每个文件的签名
verifyEntrySignature(entry);
} catch (Exception e) {
// 处理验证错误
handleVerificationError(entry, e);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
// 处理中断异常
}
}
通过并行处理和优化算法,显著提升了签名验证的效率。