Code Signing — 什么是代码签名及其工作原理

作者: IT Sectr 发布日期: 2026-04-16 阅读时间: 9 分钟

Code Signing(代码签名)——一种可执行文件的数字签名机制,保证开发者的真实性和应用程序的完整性。在Android中,每个APK文件在安装到设备或发布到Google Play之前都必须用证书签名。根据Google 2024年数据,Android支持四代签名方案:从基于JAR的v1到用于流式安装的v4。

要点

  • Code Signing——数字代码签名,证实应用程序的作者和完整性。
  • 在Android中,签名通过keystore执行——密钥和证书的存储库。
  • v2方案(APK签名方案)——自Android 7.0起的主要标准,保护APK的所有字节。
  • 密钥轮换(v3,Android 9.0+)允许在不删除应用程序的情况下更换签名密钥。
  • Google Play使用Play App Signing进行集中密钥管理。

什么是Code Signing?

Code Signing——一种加密过程,开发人员使用其数字证书对可执行代码进行签名。签名使用非对称加密创建:开发者的私钥生成数字签名,而公钥嵌入证书中。任何人都可以使用公钥验证签名,但在不破坏签名的情况下无法更改代码。

在移动开发中,代码签名具有三个功能。第一——认证:用户和平台可以识别应用程序的开发者。第二——完整性:签名后对APK的任何更改都会使签名无效。第三——可信更新:平台只允许使用与已安装版本相同证书签名的APK来更新应用程序。

法律地位

Android应用程序的数字签名具有法律意义。根据俄罗斯联邦法律(63-FZ)和欧洲eIDAS,合格电子签名等同于手写签名。然而,使用自签名证书(Android中的常见做法)对APK进行签名并不合格——它确认完整性,但从法律角度看并不确认开发者的身份。

Android签名方案:v1、v2、v3、v4

Android支持四种APK签名方案,每种方案都解决了前一版本的问题并增加了新功能。所有方案可以共存于同一个APK中——这对于向后兼容旧版Android是必要的。

v1方案(JAR签名)出现在Android 1.0中。它使用META-INF/MANIFEST.MF中的条目对APK归档中的单个文件进行签名。缺点:可以修改APK(添加或删除文件)并仅重新签名修改过的文件,而不影响其他文件的签名。这使得v1容易受到某些攻击。v2方案(APK签名方案)于Android 7.0引入,对整个APK文件进行签名,包括除签名本身之外的所有字节,从而排除了选择性修改的可能性。

方案Android特点密钥轮换
v1 (JAR)1.0+每个文件单独签名
v27.0+整个APK签名
v39.0+签名 + 轮换
v411.0+流式 + ADB

v3:签名密钥轮换

在Android 9.0中引入的v3方案解决了一个长期存在的问题:如果签名密钥被泄露或过期怎么办?以前,更改签名密钥意味着应用程序被视为新的——无法安装在现有应用程序之上。v3增加了轮换机制:可以在APK中包含由旧密钥签名的密钥更改证明(proof-of-rotation)。系统检查链并允许用新密钥更新应用程序。

Keystore和证书

Keystore——一个安全容器,包含用于应用程序签名的私钥和证书。在Android开发中,使用JKS(Java KeyStore)或PKCS12格式。Keystore使用JDK自带的keytool工具创建。存储库中的每个密钥由一个别名(alias)标识并由密码保护。

Keystore中的证书包含公钥和所有者信息:组织名称、国家、有效期。对于Android应用程序,证书可以是自签名的——Google不要求向证书颁发机构(CA)申请,这使Android与iOS不同。但是,证书的有效期必须至少为25年,因为应用程序将使用同一密钥进行更新。

bash
# 为签名创建新的keystore
keytool -genkey -v -keystore my-release.keystore \
        -alias my-app-alias \
        -keyalg RSA \
        -keysize 2048 \
        -validity 10000

# 查看keystore内容
keytool -list -v -keystore my-release.keystore

密钥格式

Android支持两种签名密钥算法:RSAECDSA。2048位密钥大小的RSA是事实上的标准,受所有Android版本支持。使用P-256曲线的ECDSA(椭圆曲线数字签名算法)在更小的密钥大小下提供相同的密码强度。从Android 9.0开始,建议使用ECDSA,因为它在移动设备上验证速度更快。

构建中的签名配置

Android Gradle Plugin中,签名通过模块级别build.gradle中的signingConfigs块进行配置。对于debug构建,Android Studio自动使用已知密码创建调试keystore。对于release构建,开发者指定其keystore的路径、密钥别名和密码。建议将密码存储在单独的配置文件中,并排除在版本控制系统之外。

现代实践是通过CI/CD进行集中签名管理。Jenkins、GitLab CI或GitHub Actions可以将keystore作为受保护工件存储,并将密码作为环境机密存储。这可以防止密钥通过仓库泄露,并在需要时简化密钥更换。

groovy
// build.gradle(应用级别)——签名配置
android {
    signingConfigs {
        release {
            storeFile file("my-release.keystore")
            storePassword System.getenv("KEYSTORE_PASSWORD")
            keyAlias System.getenv("KEY_ALIAS")
            keyPassword System.getenv("KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

多方案签名

为了最大兼容性,APK应使用所有三种方案(v1 + v2 + v3)签名。Android Gradle Plugin默认包含所有方案。仅使用v2签名的APK无法在Android 6.0及更低版本上安装。仅使用v1的APK在Android 7.0+上无法获得v2的完整性优势。包含所有方案不会使APK大小增加超过1-2%,并确保与任何设备的兼容性

Play App Signing和密钥管理

Play App Signing——一项Google Play服务,集中管理应用程序签名密钥。开发者将使用上传密钥(upload key)签名的APK上传到Google Play Console,Google Play在交付给用户之前使用分发密钥(distribution key)重新签名。这可以保护分发密钥免于丢失或泄露。

Play App Signing的优势:安全性——分发密钥存储在Google的安全存储中;轮换——可以通过控制台请求密钥更换;恢复——如果上传密钥丢失,可以生成新的。缺点:对于在Play App Signing引入之前已存在的应用程序,迁移需要创建新应用程序,因为旧的分发密钥已经在使用中。

bash
# 获取证书指纹(SHA-256)
keytool -list -v -keystore my-release.keystore \
        -alias my-app-alias | grep "SHA256"

# 通过apksigner验证APK签名
apksigner verify --verbose app-release.apk

密钥恢复

如果签名密钥丢失且未使用Play App Signing,则无法恢复更新应用程序的能力——必须使用新的包名创建新应用程序。这是使用Play App Signing的主要原因之一。Google建议将keystore备份保存在安全的离线存储中(加密USB介质、银行保险箱)。

设备上的签名验证

在安装APK时,Android会分几个阶段执行签名验证。第一——证书检查:是否过期,格式是否正确。第二——签名检查:加密签名是否与APK内容匹配。第三——与已安装版本的证书比较:如果应用程序已存在于设备上,证书必须匹配,否则安装被阻止。

验证系统内置于PackageManagerService中。在处理安装请求时,PMS从APK中提取签名,使用android.util.PackageParser类检查它,并与已安装应用程序的存储签名(如果存在)进行比较。如果不匹配,用户会收到错误"INSTALL_FAILED_UPDATE_INCOMPATIBLE"。此机制可防止替换攻击(恶意软件无法用自己的版本更新合法应用程序)。

开发者检查

开发者可以使用Android SDK Build Tools中的apksigner工具独立检查APK签名。命令apksigner verify --verbose app.apk显示APK使用哪些方案签名,证书是否有效以及签名是否与内容匹配。要以编程方式检查已安装应用程序的签名,请使用带有GET_SIGNATURES标志的PackageManager.getPackageInfo()

kotlin
// 已安装应用程序签名的编程验证
fun getAppSignature(context: Context, packageName: String): String? {
    val pm = context.packageManager
    val info = pm.getPackageInfo(
        packageName,
        PackageManager.GET_SIGNATURES
    )
    return info.signatures?.firstOrNull()?.toCharsString()
}

签名安全最佳实践

签名密钥安全是Android开发的一个关键方面。密钥泄露使攻击者可以用自己的代码签名您的应用程序更新。基本原则:永远不要将密钥存储在仓库中,不要为不同的应用程序使用相同的密钥,不要通过不安全的渠道(电子邮件、即时通讯)传输密钥。

推荐的做法是密钥分离。为每个应用程序使用单独的密钥,并为上传到Google Play使用单独的密钥(upload key)。对于debug构建,Android Studio创建通用的debug.keystore——不能用于release构建。证书的有效期应为25-30年(现行标准,经Google确认)。

实践建议
密钥存储加密介质、CI/CD机密
证书有效期至少25年
算法RSA 2048+或ECDSA P-256
分离每个应用程序单独密钥
备份Keystore离线副本

签名审计

定期检查签名链的完整性。在更换有权访问密钥的员工时,通过Google Play Console更新上传密钥。使用Google Play Integrity API等工具检查您的应用程序在用户设备上是否被篡改。该API返回签名和完整性数据,并将其发送到服务器进行验证。

常见问题

什么是Android中的Code Signing?

Code Signing——APK文件的数字签名,确认应用程序由特定开发者创建且在签名后未被修改。没有签名,APK将无法安装到设备上。

如何创建Android应用程序的签名密钥?

使用JDK中的keytool工具:keytool -genkey -v -keystore my-release.keystore -alias my-alias -keyalg RSA -keysize 2048 -validity 10000。在build.gradle的signingConfigs块中指定生成的keystore。

如果丢失签名密钥会怎样?

如果密钥丢失且未使用Play App Signing,应用程序将无法更新。必须在Google Play中创建具有新包名的新应用程序。使用Play App Signing防止密钥丢失。

v1和v2签名方案有什么区别?

v1单独签名APK内的每个文件——攻击者可以修改一个文件并仅重新签名该文件。v2签名整个APK——任何更改都会使签名无效,从而提供更高级别的安全性。

什么是Play App Signing?

Play App Signing——一项Google Play服务,集中存储应用程序的分发密钥。开发者上传使用上传密钥签名的APK,Google在交付给用户之前重新签名,保护密钥免于丢失或被盗。

总结

  • Code Signing——强制性的APK数字签名,保证应用程序的真实性和完整性。
  • Android支持四种签名方案:v1(JAR)、v2(APK签名)、v3(密钥轮换)和v4(流式)。
  • Keystore——使用keytool和RSA 2048+算法创建的安全密钥容器。
  • 密钥轮换(v3,Android 9.0+)允许在不删除应用程序的情况下更换签名密钥。
  • Play App Signing通过Google Play Console集中管理分发密钥。
  • 安装时的签名验证可防止替换攻击:证书不匹配=INSTALL_FAILED错误。
  • 密钥安全:每个应用程序单独密钥,有效期25年以上,离线副本,仓库中没有任何密钥。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读