Android - APK签名过程

128 阅读4分钟

Android 的签名过程:

1. 基本概念

// 签名文件结构
class KeystoreExample {
    val keystore = mapOf(
        "别名" to "应用标识",
        "密钥" to "私钥内容",
        "证书" to "公钥证书",
        "有效期" to "25年"
    )
}

2. 生成签名文件

# 使用 keytool 生成密钥库
keytool -genkey \
        -alias myapp \                # 别名
        -keyalg RSA \                # 算法
        -keysize 2048 \              # 密钥长度
        -validity 9125 \             # 有效期(25年)
        -keystore release.keystore   # 输出文件

3. Gradle 配置签名

android {
    signingConfigs {
        release {
            storeFile file("release.keystore")
            storePassword "store123"
            keyAlias "myapp"
            keyPassword "key123"
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

4. 签名过程

class SigningProcess {
    fun explain() {
        // 1. 计算 APK 内容摘要
        val manifest = calculateDigest("AndroidManifest.xml")
        val classes = calculateDigest("classes.dex")
        val resources = calculateDigest("resources.arsc")
        
        // 2. 生成 MANIFEST.MF
        writeToFile("MANIFEST.MF", """
            Manifest-Version: 1.0
            Built-By: Android Gradle
            
            Name: AndroidManifest.xml
            SHA-256-Digest: ${manifest.base64()}
            
            Name: classes.dex
            SHA-256-Digest: ${classes.base64()}
            
            Name: resources.arsc
            SHA-256-Digest: ${resources.base64()}
        """)
        
        // 3. 生成 CERT.SF
        val sfContent = calculateDigest("MANIFEST.MF")
        writeToFile("CERT.SF", sfContent)
        
        // 4. 使用私钥签名
        val signature = sign(sfContent, privateKey)
        writeToFile("CERT.RSA", signature)
    }
}

5. 签名验证过程

class SignatureVerification {
    fun verify(apk: File) {
        // 1. 验证 CERT.RSA
        val cert = extractCertificate(apk)
        if (!cert.verify()) throw SecurityException()
        
        // 2. 验证 CERT.SF
        val sf = extractSignatureFile(apk)
        if (!sf.matchesManifest()) throw SecurityException()
        
        // 3. 验证 MANIFEST.MF
        val manifest = extractManifest(apk)
        if (!manifest.matchesFiles()) throw SecurityException()
    }
}

MANIFEST.MF 和 CERT.SF

1. MANIFEST.MF
# MANIFEST.MF 文件内容示例
Manifest-Version: 1.0
Created-By: Android Gradle 4.0.1

Name: AndroidManifest.xml
SHA-256-Digest: 8WU2AJ678BXZN+K1YvUj789HN...

Name: res/layout/activity_main.xml
SHA-256-Digest: 9KL3QW5TY7VBXM+P2ZXCV456N...

Name: classes.dex
SHA-256-Digest: MNBV4X3ZLK9P0QWERTY12345...

功能

  • 记录 APK 中每个文件的摘要值
  • 确保文件内容完整性
  • 防止文件被篡改
2. CERT.SF
# CERT.SF 文件内容示例
Signature-Version: 1.0
Created-By: Android Gradle 4.0.1
SHA-256-Digest-Manifest: QWERTY12345...  # 整个 MANIFEST.MF 的摘要

Name: AndroidManifest.xml
SHA-256-Digest: ASDFGH67890...  # 对应 MANIFEST.MF 中该条目的摘要

Name: res/layout/activity_main.xml
SHA-256-Digest: ZXCVBN45678...

Name: classes.dex
SHA-256-Digest: POIUYT98765...

功能

  • 对 MANIFEST.MF 文件进行二次摘要
  • 防止 MANIFEST.MF 被篡改
  • 提供签名保护
3. 验证流程
class SignatureVerification {
    fun verify() {
        // 1. 验证 CERT.SF
        val sfFile = extractFile("META-INF/CERT.SF")
        val manifestFile = extractFile("META-INF/MANIFEST.MF")
        
        // 验证 MANIFEST.MF 的完整性
        val manifestDigest = calculateDigest(manifestFile)
        if (manifestDigest != sfFile.manifestDigest) {
            throw SecurityException("MANIFEST.MF 被篡改")
        }
        
        // 2. 验证 MANIFEST.MF
        val actualFiles = extractAllFiles()
        manifestFile.entries.forEach { entry ->
            val fileDigest = calculateDigest(actualFiles[entry.name])
            if (fileDigest != entry.digest) {
                throw SecurityException("文件被篡改: ${entry.name}")
            }
        }
    }
}
4. 双重保护机制
graph TD
    A[APK文件] --> B[计算文件摘要]
    B --> C[MANIFEST.MF]
    C --> D[计算MANIFEST.MF摘要]
    D --> E[CERT.SF]
    E --> F[私钥签名]
    F --> G[CERT.RSA]
5. 实际应用示例
class SigningExample {
    fun explainProcess() {
        // 1. 生成 MANIFEST.MF
        val manifest = StringBuilder().apply {
            appendLine("Manifest-Version: 1.0")
            appendLine("Created-By: Android Gradle")
            appendLine()
            
            // 为每个文件生成摘要
            files.forEach { file ->
                appendLine("Name: ${file.name}")
                appendLine("SHA-256-Digest: ${calculateDigest(file)}")
                appendLine()
            }
        }.toString()
        
        // 2. 生成 CERT.SF
        val sf = StringBuilder().apply {
            appendLine("Signature-Version: 1.0")
            appendLine("Created-By: Android Gradle")
            appendLine("SHA-256-Digest-Manifest: ${calculateDigest(manifest)}")
            appendLine()
            
            // 为 MANIFEST.MF 中的每个条目生成摘要
            manifest.entries.forEach { entry ->
                appendLine("Name: ${entry.name}")
                appendLine("SHA-256-Digest: ${calculateDigest(entry.toString())}")
                appendLine()
            }
        }.toString()
    }
}

总结:

  1. MANIFEST.MF 特点

    • 记录所有文件摘要
    • 一级完整性保护
    • 文件级别验证
  2. CERT.SF 特点

    • 对 MANIFEST.MF 再次摘要
    • 二级完整性保护
    • 整体性验证
  3. 保护机制

    • 双重摘要保护
    • 层层签名验证
    • 完整性链条
  4. 验证过程

    • 先验证 CERT.SF
    • 再验证 MANIFEST.MF
    • 最后验证实际文件
  5. 安全保证

    • 防止文件篡改
    • 防止摘要篡改
    • 确保完整性

这种双重保护机制确保了 APK 文件的完整性和安全性。

6. 流程图

[生成签名文件][准备 APK 文件][计算文件摘要][生成 MANIFEST.MF][生成 CERT.SF][使用私钥签名][生成 CERT.RSA][打包进 APK][安装时验证]

7. 详细流程图

┌─────────────────┐
│  APK 文件内容   │
└────────┬────────┘
         ↓
┌─────────────────┐    ┌─────────────┐
│  计算文件摘要   │ → │ MANIFEST.MF │
└────────┬────────┘    └──────┬──────┘
         ↓                    ↓
┌─────────────────┐    ┌─────────────┐
│   生成签名文件  │ → │   CERT.SF   │
└────────┬────────┘    └──────┬──────┘
         ↓                    ↓
┌─────────────────┐    ┌─────────────┐
│   私钥签名      │ → │  CERT.RSA   │
└────────┬────────┘    └──────┬──────┘
         ↓                    ↓
┌─────────────────────────────────────┐
│            最终 APK 文件            │
└─────────────────────────────────────┘

总结:

  1. 签名过程

    • 生成密钥库
    • 计算文件摘要
    • 生成清单文件
    • 生成签名文件
    • 使用私钥签名
  2. 文件说明

    • MANIFEST.MF:文件摘要清单
    • CERT.SF:清单文件签名
    • CERT.RSA:包含公钥证书和签名
  3. 验证过程

    • 验证证书有效性
    • 验证签名完整性
    • 验证文件一致性
  4. 安全保证

    • 完整性保护
    • 来源认证
    • 防篡改机制
  5. 注意事项

    • 密钥安全保管
    • 签名文件备份
    • 版本升级考虑
    • 签名算法选择

这就是 Android 应用签名的完整过程。签名机制确保了应用的完整性和来源可信性。

数字签名与不对称加密的区别

数字签名和加密是不同的过程。在 APK 签名中,使用的是私钥签名,公钥验证的机制。让我详细解释:

1. 签名过程

class SigningProcess {
    fun sign() {
        // 1. 计算 CERT.SF 的摘要
        val sfDigest = calculateDigest(certSF)
        
        // 2. 使用私钥对摘要进行签名
        val signature = privateKey.sign(sfDigest)
        
        // 3. CERT.RSA 包含:
        val certRSA = CertRSA(
            publicKeyCertificate = certificate,  // 公钥证书
            signature = signature               // 签名数据
        )
    }
}

2. 验证过程

class VerificationProcess {
    fun verify() {
        // 1. 从 CERT.RSA 提取公钥证书
        val certificate = certRSA.certificate
        val publicKey = certificate.publicKey
        
        // 2. 计算 CERT.SF 的摘要
        val actualDigest = calculateDigest(certSF)
        
        // 3. 使用公钥验证签名
        val isValid = publicKey.verify(
            signature = certRSA.signature,
            digest = actualDigest
        )
    }
}

3. 对比加密和签名

class Comparison {
    fun explain() {
        // 加密通信
        val encrypted = publicKey.encrypt(message)  // 公钥加密
        val decrypted = privateKey.decrypt(encrypted)  // 私钥解密
        
        // 数字签名
        val signature = privateKey.sign(message)    // 私钥签名
        val isValid = publicKey.verify(signature)   // 公钥验证
    }
}

4. 流程图

graph TD
    subgraph 签名过程
        A[CERT.SF文件] --> B[计算摘要]
        B --> C[私钥签名]
        C --> D[生成CERT.RSA]
        E[公钥证书] --> D
    end
    
    subgraph 验证过程
        F[CERT.RSA文件] --> G[提取公钥证书]
        F --> H[提取签名]
        I[CERT.SF文件] --> J[计算摘要]
        G --> K[验证签名]
        H --> K
        J --> K
    end

5. 关键区别

class KeyDifferences {
    fun explain() {
        // 1. 加密通信(保密性)
        val secret = "机密信息"
        val encrypted = publicKey.encrypt(secret)    // 公钥加密
        val decrypted = privateKey.decrypt(encrypted)// 私钥解密
        
        // 2. 数字签名(完整性、认证)
        val message = "重要文件"
        val signature = privateKey.sign(message)     // 私钥签名
        val authentic = publicKey.verify(signature)  // 公钥验证
    }
}

总结:

  1. 数字签名目的

    • 确保完整性
    • 验证来源
    • 防止篡改
  2. 加密通信目的

    • 保护隐私
    • 确保机密性
    • 防止窃听
  3. 主要区别

    • 签名:私钥签名,公钥验证
    • 加密:公钥加密,私钥解密
  4. APK 签名特点

    • 不是加密 APK 内容
    • 只是验证完整性
    • 确保来源可信
  5. 安全保证

    • 只有持有私钥者才能签名
    • 任何人都能用公钥验证
    • 签名不可伪造

这就是为什么 APK 签名使用私钥签名,而验证时使用公钥的原因。这是数字签名的标准做法,与加密通信的过程是不同的。

总体流程图

graph TD
    A[APK文件] --> B[计算文件摘要]
    B --> C[生成 MANIFEST.MF]
    C --> D[生成 CERT.SF]
    D --> E[使用私钥签名]
    E --> F[生成 CERT.RSA]
    F --> G[最终签名APK]
    
    %% 签名文件生成过程
    H[Keystore生成] --> |keytool| I[密钥对]
    I --> |私钥| E
    I --> |公钥证书| F
    
    %% 验证过程
    G --> J[安装验证]
    J --> K[验证 CERT.RSA]
    K --> L[验证 CERT.SF]
    L --> M[验证 MANIFEST.MF]
    M --> N[验证通过]

更详细的版本:

flowchart TD
    subgraph 准备阶段
        A[生成Keystore] --> B[配置Gradle签名]
        B --> C[准备APK文件]
    end
    
    subgraph 签名过程
        C --> D[计算APK文件摘要]
        D --> E[生成MANIFEST.MF]
        E --> F[生成CERT.SF]
        F --> G[私钥签名]
        G --> H[生成CERT.RSA]
    end
    
    subgraph META-INF目录
        E --> I[MANIFEST.MF]
        F --> J[CERT.SF]
        H --> K[CERT.RSA]
    end
    
    subgraph 验证过程
        L[安装APK] --> M[验证证书]
        M --> N[验证签名]
        N --> O[验证文件完整性]
        O --> P{验证通过?}
        P -->|是| Q[安装完成]
        P -->|否| R[安装失败]
    end

状态流转图:

stateDiagram-v2
    [*] --> 准备密钥
    准备密钥 --> 计算摘要
    计算摘要 --> 生成清单
    生成清单 --> 签名文件
    签名文件 --> 打包APK
    打包APK --> 验证安装
    验证安装 --> [*]
    
    state 验证安装 {
        [*] --> 验证证书
        验证证书 --> 验证签名
        验证签名 --> 验证完整性
        验证完整性 --> [*]
    }

时序图:

sequenceDiagram
    participant A as APK文件
    participant B as 构建工具
    participant C as 签名工具
    participant D as 安装器
    
    A->>B: 准备打包
    B->>C: 计算文件摘要
    C->>C: 生成MANIFEST.MF
    C->>C: 生成CERT.SF
    C->>C: 私钥签名
    C->>C: 生成CERT.RSA
    C->>A: 写入签名文件
    A->>D: 请求安装
    D->>D: 验证签名
    D-->>A: 安装结果

这些图表从不同角度展示了 Android 签名的过程,包括:

  1. 基本流程
  2. 详细步骤
  3. 状态转换
  4. 时序关系

选择合适的图表可以更好地理解整个签名过程。