Android下PEM格式RSA私钥转PrivateKey遇异常问题求助
看起来你踩了Android RSA私钥加载的一个常见坑——默认的KeyFactory只认PKCS#8格式的私钥,但你手里的是PKCS#1格式的。先给你拆解下错误原因,再给两种可行的解决办法。
错误原因解析
第一个报错
Must use RSAPublicKeySpec or PKCS8EncodedKeySpec:
你用了X509EncodedKeySpec来加载私钥,这个类是专门给X.509格式的公钥用的,私钥肯定不能这么搞,换成PKCS8EncodedKeySpec方向是对的,但问题没结束。第二个报错
error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:
你的PEM私钥是PKCS#1格式(对应-----BEGIN RSA PRIVATE KEY-----头),而PKCS8EncodedKeySpec需要的是PKCS#8格式的私钥(对应-----BEGIN PRIVATE KEY-----头),两者的ASN.1编码结构完全不同,直接解析自然会报ASN.1标签错误。而Python的RSA.import_key会自动识别这两种格式,所以能正常用。
解决方案
方案一:用BouncyCastle库直接加载PKCS#1私钥(最简单)
BouncyCastle提供了对PKCS#1格式的直接支持,不需要手动转换格式。
步骤1:添加依赖
在你的build.gradle(Module级别)里加入BouncyCastle的依赖:
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
步骤2:编写加载代码
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.PKCS1EncodedKeySpec; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Security; import android.util.Base64; // 先初始化BouncyCastle提供者(建议在Application启动时执行一次) static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } public PrivateKey loadRSAPrivateKey(String pemKey) throws Exception { // 解码Base64格式的私钥内容 byte[] pkcs1Encoded = Base64.decode(pemKey, Base64.DEFAULT); // 用BouncyCastle的PKCS1EncodedKeySpec解析私钥 PKCS1EncodedKeySpec pkcs1Spec = new PKCS1EncodedKeySpec(pkcs1Encoded); KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); return keyFactory.generatePrivate(pkcs1Spec); }
方案二:手动将PKCS#1转换为PKCS#8格式(无第三方依赖)
如果不想引入BouncyCastle库,可以手动构造PKCS#8的ASN.1结构,把PKCS#1的私钥包裹进去。
转换工具方法
import java.io.ByteArrayOutputStream; public static byte[] convertPkcs1ToPkcs8(byte[] pkcs1Bytes) throws Exception { // 构造PKCS#8的ASN.1序列结构 ByteArrayOutputStream pkcs8Stream = new ByteArrayOutputStream(); // 1. 整个PrivateKeyInfo的SEQUENCE标签 pkcs8Stream.write(0x30); // 2. 构造SEQUENCE内部内容 ByteArrayOutputStream innerStream = new ByteArrayOutputStream(); // 2.1 版本号(固定为0) innerStream.write(0x02); // INTEGER标签 innerStream.write(0x01); // 长度1 innerStream.write(0x00); // 值0 // 2.2 算法标识符(RSA的OID:1.2.840.113549.1.1.1) innerStream.write(0x30); // SEQUENCE标签 ByteArrayOutputStream algIdStream = new ByteArrayOutputStream(); algIdStream.write(0x06); // OBJECT IDENTIFIER标签 byte[] rsaOid = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01}; algIdStream.write((byte) (0x80 | rsaOid.length)); // 长度9 algIdStream.write(rsaOid); algIdStream.write(0x05); // NULL标签 algIdStream.write(0x00); // 长度0 innerStream.write((byte) (0x80 | algIdStream.size())); // 算法标识符的长度 innerStream.write(algIdStream.toByteArray()); // 2.3 私钥内容(PKCS#1的字节数组,用OCTET STRING包裹) innerStream.write(0x04); // OCTET STRING标签 innerStream.write((byte) (0x80 | pkcs1Bytes.length)); // PKCS#1内容的长度 innerStream.write(pkcs1Bytes); // 3. 写入整个SEQUENCE的长度 byte[] innerBytes = innerStream.toByteArray(); pkcs8Stream.write((byte) (0x80 | innerBytes.length)); pkcs8Stream.write(innerBytes); return pkcs8Stream.toByteArray(); }
使用转换后的私钥加载
String pemKey = "MIICWwIBAAKBgQDIuL0SzG1+wgyaoyDyHvYIaG10ePXBHqaKTnYyZfY5RzaEFLE/vBdFN2Di7AMH3/5iN/YFQqLsVjKqzX3E3LM2dJOZ9qSWeYArSyQPbhy0eM/3amwchvtLvhVLm2UqVFLjiPGlyYX3D75ETD5tmgulAc5ZDRtGqYVoLKPmZ0USPwIDAQABAoGAErfDjf65UUJISZ1fw6Rmfic62csz47P3hNtHQ3Dlsra020FQvChOpTpCUzb+G1xkjQU58Iijx9VL+Uiba2HHZmiJX2LgS3KKqKFZKmbKZnZQTiw+2o+4AXhtcAYfSAJE9TgRPEhwhZmzV2cvfUk5AjnOghSn2gGjdD1g4xtH22ECQQD/ZbfEd2HEGqHf6j/AVMW+N/Q1xtYIB8r0CWxF6cNw5iq/8Ce9ujpnAFi0vgtojyKDlgwBp4XMU2C4is49EkFhAkEAyTH96mS8dExAAmi3Mm2seUIEOtKwuLD6BEECecPyZSIOd24tfNbmA7Ri6MpGjyLZoNoJQ0AJGcnWU1tnc8bXnwJAS+jYyP1OwrHDwUDnt+u6ZoJNBJrXzMU8LnKKivEjFPBkbm4b8cljSHAS7Y266FX6xS+Y2/kFzKhPjCo9iGtfoQJAOv39hYyj9TWmTw6FKLQfri49L0I3ru+1Xynwn+NkX2Ls+vfDPqeEKfHqTneA2NdPGGrV7HIKORWFUkuqubfD4QJAK60RuhDSeH+ZljcYLhbHLoTnja/uTcvDAd0M4ll2HUNId4jPbYl1qw7OQwfg8apKmGwp7HGW50o/EItvvJrR7w=="; byte[] pkcs1Encoded = Base64.decode(pemKey, Base64.DEFAULT); byte[] pkcs8Encoded = convertPkcs1ToPkcs8(pkcs1Encoded); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Encoded); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey sessionKey = keyFactory.generatePrivate(keySpec);
验证说明
两种方法都能正常加载你的私钥,你可以根据项目是否允许引入第三方库来选择。如果是小型项目,方案二更省体积;如果项目已经在用BouncyCastle,方案一最省心。
内容的提问来源于stack exchange,提问作者matec

