ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Google Play签名密钥迁移:pepk导出与密钥更新避坑指南

Google Play签名密钥迁移:pepk导出与密钥更新避坑指南

简介:pepk签名工具源码包,面向需要更新Google Play应用签名的Android开发者,用于处理上架与密钥升级中的签名生成、校验及迁移问题。压缩包共含2000个文件,其中1999个为Java源文件,1个为txt说明文件,整体压缩后仅5.02MB;Java代码覆盖TLS协议工具、PKIX证书路径验证、AES与GOST等加密算法实现,txt说明文件则梳理源码结构与使用要点。目前已有372人学习下载,既适合刚接触签名机制的初学者,也适合需要二次定制签名流程的中高级开发人员。研读源码可理解pepk在证书链校验、加密摘要计算以及最终输出格式生成上如何对接Google Play签名体系,从而独立排查签名失败或构建自定义工具。资源体量紧凑、模块划分清晰,可直接导入工程查看关键算法,对移动应用交付与市场发布环节的签名管理有实际参考价值。

1. 把“pepk源码”拆开看:这是Play签名托管的钥匙转换器,不是签名工具

“pepk”这个名字在签名链路里经常被误解。它不是一个给APK做签名的工具,而是把开发者手里那把“签名主钥匙”从本地安全导出的转换器:你用它的可执行jar,配上一串参数,把JKS密钥库里的私钥加密成一份.pepk文件,交到Google Play控制台之后,Play App Signing才真正接管签名动作。反直觉的点在于:本地证书导出得越干净,你越不需要把release keystore直接发给Google,也就越少一次密钥泄漏的风险。需要做这件事的通常是三类人:存量应用要迁移到Play App Signing、应用密钥需要轮换更新、或者刚接手一个没有文档的旧项目要重建发布链路。这篇按“原理 → 落地命令 → 更新签名 → 避坑 → 验证”的顺序一次讲完。

2. 为什么Play坚持要.pepk:两把钥匙的分工,以及pepk的职责边界

2.1 两把钥匙的分工:Upload Key负责上传,App Signing Key负责真正签名

在Play Console的“应用完整性”页面开启Play App Signing之后,签名体系会被拆成两层。第一层叫上传密钥(Upload Key),它只证明“这次上传包是你这个开发者发出来的”,作用范围到控制台接收文件为止;第二层叫应用签名密钥(App Signing Key),由Google托管,最终每一个分发给用户的APK或者从AAB生成的安装包都用它签名。

这两层拆开之后,最直接的收益是:上传密钥可以随时换,只要不动应用签名密钥,老用户升级安装包时就永远不会撞上“更新包签名不一致”这个Android安装器最头疼的错误。反过来说,如果你没开Play App Signing,keystore一旦丢失或者泄露,整个应用的身份就得换,几万老用户会因为签名对不上而无法覆盖安装,只能卸载重装,数据全部清空。这个代价在Android 11之后的机型上尤其明显,因为安装器对签名不一致的拦截几乎是零容忍的。

所以,一旦决定走Play App Signing这条路,本地那把release keystore就不再是“直接签出最终包”的钥匙,而是变成用来生成上传密钥和托管材料的根凭据。它仍然重要,但重要程度从“每台机器都要用”降到“只在迁移和导出时用一次”。很多人没转过这个弯,才会出现后面章节里那些拿错钥匙、导出错文件的翻车现场。

2.2 pepk的职责边界:只做“导出与加密”,不碰签名动作

pepk全称Play Encryption Public Key,核心动作只有一个:把本地JKS/PKCS12里的私钥条目,用Google下发的RSA公钥加密,输出成.pepk文件。这个文件里不是你的keystore本体,也不是APK签名,而是“被加密过的私钥+证书”的封装。上传到控制台后,Google用自己的私钥解开,得到你的应用签名密钥,之后所有面向用户的分发包都由它签名。

理解这个边界很重要,因为我在实际排查里见过两种误用。第一种是把pepk当成签名工具,拿着它去签AAB,输出文件根本没有任何签名块;第二种是跑完pepk之后以为本地keystore就没用了,随手删掉,结果Play侧还没有完成迁移,两边都拿不到密钥,只能走密钥轮换流程重新生成一把新钥匙。pepk自始至终不签名、不校验、不轮换,它的输入是jks、输出是pepk,仅此而已。

2.3 什么时候需要跑pepk:新应用上线、存量迁移、密钥重置

跑pepk的时机有三种,参数基本一致,差别在控制台入口。第一种是新应用上线:在Play Console创建应用后选择“由Google管理我的签名密钥”,构建AAB、下载加密公钥、导出pepk上传,流程最顺。第二种是存量应用迁移:老应用已经在Play上架,原来是自签名上传APK,现在要切到Play App Signing,控制台会引导你导出一份pepk,把本地签名密钥“交给”Google托管。第三种是密钥重置后重建导出包:当你主动重置上传密钥或者轮换App Signing Key之后,需要重新生成新的pepk或者上传新的证书,否则旧记录和新本地密钥就对不上。

判断自己属于哪一种,最简单的办法是看一眼控制台“应用完整性”页面的状态:如果写的是“应用签名密钥可由Google管理”但本地有旧keystore,那就是迁移场景;如果页面显示已经托管,但你换了一台电脑要重建发布环境,那通常不需要重新跑pepk,只需要把上传密钥证书重新上传即可。这个区分直接决定了你接下来是走第3章的导出流程,还是走第4章的重置流程。

3. 从源码到命令:用pepk生成可用导出包的完整落地步骤

3.1 先读“源码”:用jar tf和javap核对入口,避免版本黑匣子

pepk官方以jar包形式下发,源码没有完整公开,社区里能搜到一些对它的逆向解读和第三方重写版本,但我不建议在生产环境用第三方实现。更可靠的做法是:拿到jar之后先用JDK自带的工具把它的结构和入口类读一遍,确认参数名和Main-Class,而不是直接对着网上的命令盲跑。版本不同,参数可能是--encryption-key也可能是--rsa-aes-encryption-keys,这一步能省掉后面大量“玄学报错”。

# 第一步:列出jar里的类,筛出与入口、加解密相关的类 jar tf pepk.jar | grep -iE "main|Pepk|Encrypt|Signing" # 第二步:反编译入口类的方法签名,确认支持哪些命令行参数 javap -p -c com.google.play.signing.pepk.PepkSigningTool 2>/dev/null | head -80

逻辑说明:jar tf打印jar包内所有条目,能看到包名结构;javap -p -c反汇编指定类的方法和字节码。如果提示类找不到,说明入口类名不对,回到上一条输出里去找带Main-Class字样的类。这一步的目的是把黑匣子打开一条缝,至少确认你手上这个jar认哪种参数写法,再决定用旧版还是新版命令。参数说明:-p显示私有成员,-c输出字节码,head -80控制输出长度,避免刷屏。

3.2 最小可用命令:从JKS导出.pepk的六个参数逐个拆

确认jar没问题之后,最常见的导出命令长这样。执行前先确认三件事:keystore文件存在、alias拼写正确、从Play控制台下载的加密公钥文件是.pem格式。

java -jar pepk.jar \ --keystore=/secure/release.jks \ --keystore-pass=file:/secure/keystore-pass.txt \ --key-alias=release \ --rsa-aes-encryption-keys=/secure/play_encryption_key.pem \ --output=/secure/upload-keys/signing-key.pepk

逻辑说明:把release.jks里的release别名条目取出来,用play_encryption_key.pem里的公钥加密,写到signing-key.pepk。这里--keystore-pass=file:...是密码文件用法,比直接明文写在命令行安全,因为命令行参数会出现在shell历史里,密码文件可以单独设置权限。如果下载到的公钥是最旧版格式,控制台页面给的是一串base64字符串,这时才需要用--encryption-key代替--rsa-aes-encryption-keys。判断标准很简单:拿到的是一整个-----BEGIN PUBLIC KEY-----块就用新参数,拿到一长行字符串就用旧参数。

参数说明:--keystore指本地jks或pkcs12文件;--key-alias必须和keytool -list里显示的一致,大小写敏感;--output的父目录必须已经存在,jar不会帮你建目录;--rsa-aes-encryption-keys指向的pem文件在Play Console的“应用完整性 → Play App Signing → 加密密钥”里下载,不是上传密钥证书,这俩在同一个页面上,很多新手会下错。

3.3 把参数收进配置文件的封装脚本:密码不落命令行

临时跑一次可以手敲命令,但如果这个导出动作要复现,比如CI流程或者换人交接,建议直接把整个导出过程封装成一个脚本,参数从外部变量读入,密码只走file:或env:前缀。

#!/usr/bin/env bash set -euo pipefail PEPK_JAR="${PEPK_JAR:?未指定 pepk.jar 路径}" KS="${KS_FILE:?未指定 keystore 路径}" KS_ALIAS="${KS_ALIAS:?未指定 alias}" KS_PASS_FILE="${KS_PASS_FILE:?未指定密码文件}" PEM_FILE="${PEM_FILE:?未指定加密公钥 pem}" OUTPUT_PEPK="${OUTPUT_PEPK:?未指定输出文件路径}" if [ ! -f "$KS_PASS_FILE" ]; then echo "密码文件不存在: $KS_PASS_FILE" exit 1 fi umask 077 java -jar "$PEPK_JAR" \ --keystore="$KS" \ --keystore-pass="file:$KS_PASS_FILE" \ --key-alias="$KS_ALIAS" \ --rsa-aes-encryption-keys="$PEM_FILE" \ --output="$OUTPUT_PEPK" echo "导出完成: $OUTPUT_PEPK" sha256sum "$OUTPUT_PEPK"

逻辑说明:set -euo pipefail保证任何一步失败脚本马上退出,不会带着半成品继续跑;${VAR:?}的写法在变量缺失时直接报错退出,避免空参数导致pepk读错文件;umask 077让新生成的pepk文件只有当前用户可读写,防止导出物落到共享目录里被其他人顺手拷走;最后的sha256sum输出哈希值,方便和上传后控制台显示的值做初步比对。

这个脚本看起来比直接跑一条命令啰嗦,但它解决的是发布链路里最常见的隐患:密码泄露和接口不可复现。把KS_PASS_FILE放到只有发布负责人能读的目录,脚本本身可以进代码库,别人接手时不需要知道密码也能完成导出生效流程。血泪经验是:直接把密码写进命令行的团队,三个月后一定有人把整段shell history贴到聊天工具里。

3.4 拿到.pepk之后:上传到Play Console的下一步

导出成功后,登录Play Console进入目标应用的“应用完整性 → 设置 → 迁移应用签名密钥”或“导出并上传密钥”流程,把pepk文件拖进去。提交后控制台会解析出两个证书指纹:一个是App Signing Key的,一个是Upload Key的。这时候建议立刻截图或者复制页面上的SHA256指纹,存进团队的密钥管理记录里,因为后续换电脑、重建环境、校验签名,用的都是这串指纹而不是文件本身。

如果上传时报“无法解析本地签名密钥”之类的错误,90%是alias大小写不对,或者pem文件下载的不是当前版本。回到3.2的命令,用keytool -list -v -keystore release.jks确认alias,再从控制台重新下载一次加密公钥,重新导出。注意,控制台里的“加密公钥”和“上传密钥证书”是两项,导出pepk只需要前者,很多人在这一步拿错文件,导出产物上传后照样报格式错误。

4. 更新Google Play签名:密钥轮换与上传密钥重置的实操路径

4.1 先分清“更新签名”的两种诉求,搞错等于翻车

“更新google play签名”这句话背后的诉求可能完全不同,动手前必须分清楚。第一诉求是轮换App Signing Key:应用签名密钥本身泄露,或者合规要求强制更换,这时用户端安装包签名会变化,已安装的老用户无法覆盖安装新包,只能引导卸载重装,影响面巨大,属于最后手段。第二诉求是重置上传密钥:团队人员变动、旧keystore密码失守、CI环境要迁移,但应用签名密钥不动,用户端签名不变,只是以后上传包的校验钥匙换一把,这是日常最常遇到的“更新签名”。

我用一张表把两者的区别列出来,方便对照决策:

操作对象用户端安装包签名老用户覆盖安装典型触发场景控制台入口
重置上传密钥不变正常人员离职、CI迁移、upload keystore疑似泄露应用完整性 → 重置上传密钥
轮换App Signing Key变更不兼容,需卸载重装托管密钥泄露、平台合规要求应用完整性 → 轮换密钥

分清这两种之后,后面所有操作步骤才有意义。如果只是想换一把“团队自己手里的钥匙”,却误走了轮换流程,等于给全量用户制造了一次强制卸载重装,这个翻车代价几乎不可逆。

4.2 密钥轮换的标准流程:下载新证书,本地重签,重新上传

只有确定要动App Signing Key时才走这一步。在Play Console的“应用完整性 → 轮换密钥”里发起轮换后,Google会生成一把新的应用签名密钥,并提供新证书下载入口。随后要做的是:用新证书在本地重新生成keystore、对新包重新签名、再上传到Play。

# 生成新的上传密钥库,注意这里是“上传密钥”,用户端签名仍由新App Signing Key决定 keytool -genkeypair -v \ -keystore upload_keystore_new.jks \ -alias upload \ -keyalg RSA \ -keysize 2048 \ -validity 10000 # 导出新证书,后续要上传到控制台 keytool -export -rfc \ -alias upload \ -keystore upload_keystore_new.jks \ -storepass CHANGE_ME \ -file upload_cert_new.pem # 用新keystore重新签名AAB/APK(AGP构建产物必须以新证书签名后才能上传) apksigner sign \ --ks upload_keystore_new.jks \ --ks-key-alias upload \ --ks-pass pass:CHANGE_ME \ --out app-release-resigned.aab \ app-release-unsigned.aab

逻辑说明:轮换之后Play侧的新App Signing Key已经生效,本地所有上传包都必须用新上传密钥签名。keytool -genkeypair生成新密钥对,-validity 10000大约是27年,覆盖应用生命周期;keytool -export导出证书给控制台做匹配;apksigner sign把未签名的构建产物签成可上传的包。参数说明:-keyalg RSA -keysize 2048是Play支持的常见组合,不要为了“更安全”选4096或ECC,控制台只认RSA 2048,容易在后续校验时被拒;apksigner是Android SDK build-tools里的工具,不要在jarsigner上浪费时间,它对AAB的处理经常出现签名块格式兼容问题。

轮换后有一个硬性时间点:轮换流程一旦在控制台确认,旧App Signing Key签出的包就不再被Play接受用于新上传,但已经分发给用户的旧包仍然装在用户设备上。所以轮换要选在发版窗口关闭后立刻执行,然后马上用新证书出一个全量包上传,让Play用它应对后续用户下载。这个窗口期里如果继续发新版本,会出现一部分用户拿到新签名包后无法覆盖安装的情况,必须在发布说明里明确提示。

4.3 上传密钥重置:给团队换钥匙的后悔药与三天信任期

日常场景更常见的是重置上传密钥。过程分四步:在控制台申请重置、生成本地新keystore、上传新证书、填写账号安全码确认。提交后Play不会立刻吊销旧上传密钥,而是保留一段信任期,常见做法是保留3天,这段期间新旧上传密钥都有效。这样设计是为了给CI系统一个过渡窗口,但你也会因此踩到“更新包签名不一致”的坑:如果你在第1天先手动用新key传了一个包,第2天CI还在用旧key自动上传,两个包签名来源不同,用户收到更新时就可能报错。

# 生成新的上传密钥库 keytool -genkeypair -v \ -keystore upload_keystore_v2.jks \ -alias upload_v2 \ -keyalg RSA \ -keysize 2048 \ -validity 10000 # 导出证书用于控制台上传 keytool -export -rfc \ -alias upload_v2 \ -keystore upload_keystore_v2.jks \ -storepass CHANGE_ME \ -file upload_cert_v2.pem

逻辑说明:这两条命令生成的是“上传密钥”,和4.2里生成upload keystore的目的一致,但不会触动App Signing Key。生成后把upload_cert_v2.pem上传到控制台,填入安全码,提交即可。参数说明:-storepass这里为了演示直接写在命令行,生产环境请改成-storepass:env或受保护的密码文件;-validity 10000对上传密钥同样适用,它只签上传动作,不签用户包,过期风险极低。

重置完成后最重要的动作是同步CI变量。跑到你CI系统的签名配置里,把旧的keystore路径、alias、密码全部换成新值,然后发一个测试包到内部测试轨道验证。注意不要急着删掉旧keystore文件,信任期内两边都能上传,但一旦发现CI新配置有问题,旧key还能做紧急回退。这个“后悔药”只在信任期内有效,过了窗口再想用旧key,控制台会直接拒绝上传。

5. 签名更新避坑清单:五个让老手也翻车的真实现场

5.1 “更新包签名不一致”来自CI里那把旧的upload keystore

现象:某个版本发布后,用户反馈覆盖安装失败,报错提示“更新包签名不一致”,但本地手工打包上传又是成功的。原因是CI系统里还配着旧的上传密钥,重置上传密钥后你在本地用了新key打包,CI自动构建还在用旧key签名,两个包到了Play侧后用户端出现签名来源漂移。解决方法是重置后第一时间全量替换CI里的签名变量,并且在信任期内用旧key发一个明确标记为“过渡验证”的包到内部轨道,确认两条链路都不再产出混淆签名。别指望靠记忆同步,把keystore指纹写进CI构建脚本的checklist里,每次构建失败时先比对指纹再查代码。

5.2 pepk报“加密公钥格式无效”,大概率是pem新旧混用

现象:跑导出命令时抛CryptoException或者提示无法解析加密公钥,命令看着没问题,keystore和alias也没错。原因是下载的pem文件和pepk.jar版本不匹配:新版jar只认--rsa-aes-encryption-keys参数并接收完整PEM块,旧版jar需要--encryption-key参数接收base64字符串;或者你下载的pem是上次轮换前的旧公钥。解决方法是回到控制台“应用完整性”页面重新下载当前加密密钥,检查文件头是不是-----BEGIN PUBLIC KEY-----,再对照3.1里的javap输出确认jar支持的参数名。下载后建议用grep -c "BEGIN PUBLIC KEY"做一次前置校验,文件不对就当场停下,别继续往下跑。

5.3 轮换后测试机装不上包:本地测试签名与分发签名脱节

现象:App Signing Key轮换后,开发机和测试机上adb install新包报签名不一致,卸载重装又舍不得测试数据。原因是本地开发/测试环境还在用旧应用签名密钥签出的调试包,或者没把调试配置切到新上传证书。解决方法是把测试轨道的分发包统一用新上传证书重签,调试期别开Play App Signing的用户端签名;如果测试机必须保留旧数据,就保留一条稳定通道继续发布旧签名包,等下一个版本再统一切换。轮换这类操作最好安排在版本号跳大版的窗口,把“签名切换”和“强制升级”绑在一起,止损面最小。

5.4 密码里的特殊字符与中文路径:pepk最脆弱的输入

现象:导出命令在macOS上跑得好好的,换到Windows PowerShell就报错,或者导出成功但上传时提示文件损坏。原因是keystore密码里含有$、&、!等字符,在shell里被解释成变量或历史展开;中文路径在Windows控制台编码下传到pepk内部变成乱码文件名。解决方法是所有密码一律走file:前缀,路径目录只用ASCII字符;Windows下推荐用Git Bash而不是cmd执行导出脚本,可以少踩一半编码坑。这里没有玄学,凡是没进配置文件而直接手敲的参数,早晚会在某个环境的奇怪字符上出问题。

5.5 一个keystore多个alias:导出成功却不匹配

现象:pepk导出命令正常执行、控制台也显示上传成功,但后续构建出的包在Play侧校验时提示密钥与历史记录不匹配。原因是同一个keystore文件里存了多个alias,比如有调试用的、有旧项目遗留的,导出时用了一个能解密但对不上的条目。解决方法是导出前先执行keytool -list -v -keystore release.jks,核对目标alias的SHA256指纹,把这个指纹和Play控制台的记录比对通过后再跑pepk。记录指纹这一步别省,项目交接时新接手的人就是靠这串指纹确认自己有没有拿错钥匙。

6. 收尾验证:把apksigner和Play控制台的指纹对齐

签名更新操作正确与否,最后都要落到证书指纹上。三个地方必须对照一致:本地keystore里的证书、最终产出的AAB/APK携带的证书、Play Console“应用完整性”页面上显示的证书。用一个清单一口气核完:

检查对象命令看什么
本地keystorekeytool -list -v -keystore release.jks -alias release输出里的SHA256指纹
AAB内嵌证书keytool -printcert -jarfile app-release.aab签名块里的SHA256指纹
APK证书apksigner verify --print-certs app-release.apk用--print-certs打印指纹,同时验证签名完整
Play控制台应用完整性 → Play App Signing 页面页面展示的App Signing Key与Upload Key指纹

我习惯在每次上传前把四个指纹贴到发布检查单里,尤其是轮换和重置之后,这三处只要有任何一处不一致,就得停下来查,而不是赌一把传上去再说。实际执行时最顺手的是把apksigner verify --print-certs接在CI发布job的最后一步,签名校验失败直接fail整个流水线,省得人工比对。

这套流程跑下来,你会发现pepk的导出只是整个签名链路里最机械的一环,真正决定它安全性的反而是周边的规矩:密码进文件、指纹进检查单、旧密钥留过渡期。早期做项目时我直接把release keystore发给同事让他传控制台,后来那只钥匙在五台电脑间复制了一圈才被想起来要吊销,那次之后就再没让keystore离开过保险柜。现在每次轮换或重置后,我会先改CI、再发测试包、最后对照指纹,顺序不乱,也就再没遇到签名翻车。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进