PEPK 上传应用签名密钥踩坑:RSA/NONE/OAEPWithSHA1AndMGF1Padding 报错排查全过程
在 Google Play 上传应用签名密钥时,PEPK 工具报 Cannot find any provider supporting RSA/NONE/OAEPWithSHA1AndMGF1Padding。本文记录从该错误起的完整排查与解决过程:注册 BouncyCastle、发现内置 BC 被裁剪、改用完整 bcprov 库,附每一步的错误输出与验证证据。
背景
在 Google Play 后台「应用签名偏好设置」中选择 「从 Java 密钥库导出并上传密钥」,需要用 Google 提供的 PEPK(Play Encrypt Private Key)工具,把本地 keystore 里的私钥导出并用 Google 的公钥加密成 output.zip 再上传。
官方给的命令模板是:
java -jar pepk.jar \
--keystore=foo.keystore \
--alias=foo \
--output=output.zip \
--include-cert \
--rsa-aes-encryption \
--encryption-key-path=/path/to/encryption_public_key.pem
按项目实际 keystore 参数替换后(本机 keystore 为 signature.jks,alias 为 kkshort),命令能正常读取密码并进入加密流程,但随即抛出算法找不到的异常。本文从这个错误开始,记录完整的排查与解决过程。
环境
| 项 | 值 |
|---|---|
| 操作系统 | Windows 10 |
| JDK | Oracle JDK17.0.15(D:\Softwares\JDK\jdk17.0.15) |
| 工具 | pepk.jar(Google Play Console 下载的可运行版) |
| 工作目录 | D:\Code(放置 pepk.jar、encryption_public_key.pem) |
问题:Cannot find any provider supporting RSA/NONE/OAEPWithSHA1AndMGF1Padding
运行命令后,两次密码输入正常,但加密阶段报错:
D:\Code>java -jar pepk.jar --keystore=AltaTV/android/signature.jks --alias=kkshort --output=output.zip --include-cert --rsa-aes-encryption --encryption-key-path=encryption_public_key.pem
Enter password for store 'signature.jks':
Enter password for key 'kkshort':
Error: Unable to export or encrypt the private key
java.security.NoSuchAlgorithmException: Cannot find any provider supporting RSA/NONE/OAEPWithSHA1AndMGF1Padding
at java.base/javax.crypto.Cipher.getInstance(Cipher.java:571)
at com.google.wireless.android.vending.developer.signing.tools.extern.export.ExportEncryptedPrivateKeyTool.encryptPrivateKeyWithCkmRsaAesKeyWrapEncryption(ExportEncryptedPrivateKeyTool.java:284)
at com.google.wireless.android.vending.developer.signing.tools.extern.export.ExportEncryptedPrivateKeyTool.run(ExportEncryptedPrivateKeyTool.java:213)
at com.google.wireless.android.vending.developer.signing.tools.extern.export.ExportEncryptedPrivateKeyTool.main(ExportEncryptedPrivateKeyTool.java:165)
初步分析
RSA/NONE/OAEPWithSHA1AndMGF1Padding 这个加密变换(transformation)不是 JDK 自带 SunJCE 提供的——SunJCE 只提供 RSA/ECB/OAEPWithSHA-1AndMGF1Padding。RSA/NONE/... 这种写法由 BouncyCastle 提供。报错说明当前 JDK 里没有注册 BouncyCastle 这个 JCE 安全提供者(Security Provider)。
先确认 JDK 版本和已注册的提供者列表:
# 查看 java 版本
java -version
# java version "17.0.15" 2025-04-15 LTS
# 查看 java.security 中已注册的 provider
Select-String -Path "$env:JAVA_HOME\conf\security\java.security" -Pattern '^security\.provider\.\d+='
输出(默认 13 个提供者,没有 BouncyCastle):
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
security.provider.4=SunJSSE
security.provider.5=SunJCE
security.provider.6=SunJGSS
security.provider.7=SunSASL
security.provider.8=XMLDSig
security.provider.9=SunPCSC
security.provider.10=JdkLDAP
security.provider.11=JdkSASL
security.provider.12=SunMSCAPI
security.provider.13=SunPKCS11
再确认 pepk.jar 内部是否自带 BouncyCastle 的类:
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead("D:\Code\pepk.jar")
($zip.Entries | Where-Object { $_.FullName -like "org/bouncycastle/*" -and $_.FullName.EndsWith(".class") }).Count
$zip.Dispose()
输出:
pepk.jar 内 BouncyCastle .class 数量: 4337
含 BouncyCastleProvider.class: True
结论:BouncyCastle 的类就在
pepk.jar里(含BouncyCastleProvider.class),只是没有被注册为 JCE 提供者。看起来只要把它注册进去就能解决。
尝试一:注册 BouncyCastle 提供者(部分有效,但仍失败)
JDK 允许通过 -Djava.security.properties=<file> 在单次运行时叠加安全配置(不改动全局 JDK),前提是 java.security 中开关为 security.overridePropertiesFile=true(JDK 默认开启,已确认)。
创建叠加文件 D:\Code\bc.security,把 BouncyCastle 追加为第 14 个提供者:
security.provider.14=org.bouncycastle.jce.provider.BouncyCastleProvider
重新运行(在 -jar 前加 -Djava.security.properties=bc.security):
D:\Code>java -Djava.security.properties=bc.security -jar pepk.jar --keystore=AltaTV/android/signature.jks --alias=kkshort --output=output.zip --include-cert --rsa-aes-encryption --encryption-key-path=encryption_public_key.pem
Enter password for store 'signature.jks':
Enter password for key 'kkshort':
Error: Unable to export or encrypt the private key
java.security.NoSuchAlgorithmException: Cannot find any provider supporting RSA/NONE/OAEPWithSHA1AndMGF1Padding
at java.base/javax.crypto.Cipher.getInstance(Cipher.java:571)
...
依然报同样的错。 为了避免反复输密码,写一个小测试程序精确定位:BC 到底注册成功了没有、这个算法能不能找到。
// CipherTest.java
import javax.crypto.Cipher;
import java.security.Security;
import java.security.Provider;
public class CipherTest {
public static void main(String[] args) throws Exception {
Provider bc = Security.getProvider("BC");
System.out.println("BC provider = " + bc
+ " (version: " + (bc == null ? "NULL" : bc.getVersionStr()) + ")");
if (bc != null) {
System.out.println("==== BC services containing RSA/OAEP ====");
for (Provider.Service s : bc.getServices()) {
String algo = s.getAlgorithm();
if (s.getType().equals("Cipher")
&& (algo.contains("RSA") || algo.contains("OAEP"))) {
System.out.println(" Cipher." + algo);
}
}
}
String[] tries = {
"RSA/NONE/OAEPWithSHA1AndMGF1Padding",
"RSA/NONE/OAEPWithSHA-1AndMGF1Padding",
"RSA/ECB/OAEPWithSHA1AndMGF1Padding",
"RSA/NONE/OAEPPadding",
};
for (String t : tries) {
try {
Cipher c = Cipher.getInstance(t);
System.out.println(" OK " + t + " -> " + c.getProvider().getName());
} catch (Exception e) {
System.out.println(" FAIL " + t + " -> " + e.getClass().getSimpleName());
}
}
}
}
编译并运行(把 pepk.jar 放到类路径,让它提供 BC 类):
javac CipherTest.java
java "-Djava.security.properties=D:\Code\bc.security" -cp "D:\Code\pepk.jar;." CipherTest
输出(关键证据):
BC provider = BC version 1.645 (version info: 1.645)
==== BC services containing RSA/OAEP ====
Cipher.RSA
Cipher.RSA/OAEP
Cipher.RSA/PKCS1
Cipher.RSA/ISO9796-1
Cipher.RSA/1
Cipher.RSA/2
Cipher.RSA/RAW
==== try transformations ====
FAIL RSA/NONE/OAEPWithSHA1AndMGF1Padding -> NoSuchAlgorithmException
FAIL RSA/NONE/OAEPWithSHA-1AndMGF1Padding -> NoSuchAlgorithmException
OK RSA/ECB/OAEPWithSHA1AndMGF1Padding -> SunJCE
FAIL RSA/NONE/OAEPPadding -> NoSuchAlgorithmException
真正的根因
BouncyCastle 确实注册成功了(provider 列表里出现了 BC),但它是 Google 用 singlejar 打包时裁剪过的 BC 1.645:只注册了笼统的 Cipher.RSA/OAEP,缺少 RSA/NONE/OAEPWith... 这种带完整参数的变换实现。所以即便注册了 BC,RSA/NONE/OAEPWithSHA1AndMGF1Padding 仍然解析不出来。
所以问题不在「有没有注册 BC」,而在「注册进来的 BC 是残缺的」。解决方向:用一个完整的、独立的 BouncyCastle 库替换它。
尝试二:改用完整的 bcprov 库(成功)
从 Maven Central 下载完整的 BouncyCastle 提供者库:
Invoke-WebRequest `
-Uri "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk18on/1.78.1/bcprov-jdk18on-1.78.1.jar" `
-OutFile "D:\Code\bcprov.jar"
# 下载成功: 8324412 bytes
用测试程序验证——把 bcprov.jar 放在类路径最前面,让完整 BC 覆盖 pepk 内置的残缺 BC 1.645:
java "-Djava.security.properties=D:\Code\bc.security" -cp "D:\Code\bcprov.jar;D:\Code\pepk.jar;." CipherTest
输出(验证通过):
BC provider = BC version 1.7801 (version info: 1.7801)
==== BC services containing RSA/OAEP ====
Cipher.RSA
Cipher.RSA/ISO9796-1
Cipher.RSA/RAW
Cipher.RSA/OAEP
Cipher.RSA/PKCS1
Cipher.RSA/1
Cipher.RSA/2
==== try transformations ====
OK RSA/NONE/OAEPWithSHA1AndMGF1Padding -> BC
OK RSA/NONE/OAEPWithSHA-1AndMGF1Padding -> BC
OK RSA/ECB/OAEPWithSHA1AndMGF1Padding -> SunJCE
OK RSA/NONE/OAEPPadding -> BC
BC version 1.7801,且 RSA/NONE/OAEPWithSHA1AndMGF1Padding -> BC 成功解析。
最终解决方案
在 D:\Code 目录准备好三样东西:
| 文件 | 说明 |
|---|---|
pepk.jar | Google Play Console 下载的 PEPK 工具(可运行版,不是源码包) |
bcprov.jar | 从 Maven 下载的完整bcprov-jdk18on-1.78.1.jar |
bc.security | 一行内容:security.provider.14=org.bouncycastle.jce.provider.BouncyCastleProvider |
运行最终命令(注意:用 -cp 而非 -jar,并显式指定主类):
java -Djava.security.properties=bc.security ^
-cp "bcprov.jar;pepk.jar" ^
com.google.wireless.android.vending.developer.signing.tools.extern.export.ExportEncryptedPrivateKeyTool ^
--keystore=AltaTV/android/signature.jks ^
--alias=kkshort ^
--output=output.zip ^
--include-cert ^
--rsa-aes-encryption ^
--encryption-key-path=encryption_public_key.pem
相比最初命令的三处关键改动:
-cp "bcprov.jar;pepk.jar"替代-jar pepk.jar——把完整 BouncyCastle 放在类路径最前,覆盖 pepk 内置的残缺 BC 1.645;类路径分隔符 Windows 用;,Linux/macOS 用:。- 因为不再用
-jar,需要显式写主类名com.google.wireless.android.vending.developer.signing.tools.extern.export.ExportEncryptedPrivateKeyTool。 - 保留
-Djava.security.properties=bc.security把 BouncyCastle 注册为 JCE 提供者。
运行时按提示两次输入 keystore 密码与 key 密码,即可正常生成 output.zip,回到 Play Console 上传该文件即可完成上传密钥。
排查过程总结
| 阶段 | 现象 | 根因 | 解决 |
|---|---|---|---|
| 问题 | NoSuchAlgorithmException: ... RSA/NONE/OAEPWithSHA1AndMGF1Padding | JDK 17 未注册 BouncyCastle | 需要注册 BC 提供者 |
| 尝试一 | 注册 BC 后仍报同样的错 | pepk 内置的BC 1.645 被裁剪,缺 RSA/NONE/OAEP 完整变换 | 需换完整 BC 库 |
| 尝试二 | 算法成功解析 → BC | 完整bcprov 1.78.1 提供该变换 | ✅ 用-cp 把完整 bcprov 放类路径最前 + 注册 BC |
经验教训
NoSuchAlgorithmException分两层:一层是「提供者压根没注册」,另一层是「提供者注册了但缺这个具体算法」。看到 provider 列表里已有BC却仍报错时,要往第二层想。- 不要盲目反复重跑需要交互输入密码的命令:写一个几行的
Cipher.getInstance测试程序,把「provider 是否注册」「算法是否可用」一次性打印出来,比一遍遍输密码试错高效得多。 java -jar会忽略-cp:-jar模式下类路径只有那个 jar,要加额外依赖必须改用-cp并显式写主类。-Djava.security.properties=(单个等号)是叠加,==(双等号)是整体替换:单等号方式只对本次运行生效、不污染全局 JDK 配置,更安全可回滚。- Google 打包的工具内可能带裁剪过的第三方库,遇到「类在、功能缺」的诡异现象时,用一个独立的完整依赖覆盖它往往是最快的路。