使用PEM文件生成PrivateKey时遇版本不匹配异常的Java问询
问题:PEM私钥生成JWT时出现版本不匹配异常
问题详情
使用给定的.pem私钥文件生成JWT时,在执行KeyFactory.generatePrivate(keySpec)步骤抛出InvalidKeySpecException,核心错误为version mismatch: (supported: 00, parsed: 01)。
相关文件与代码
mysecret1.pem文件内容
-----BEGIN PRIVATE KEY----- MIGkAgEBBDAz2He6GHzqEe+a2MAl/3QyuvnCsGQrFYKmP/F9mX7lhjYBqN/wJBQd ppqjhVPFiF+gBwYFK4EEACKhZANiAAQvEqdP8J+IkWrx2WA9EEblRiZPaRY9drWI aNFGI24rMBbx5SPd+OkLEOPx1Z5QOJlhdkKtwGsNZRklXGBwFwy/sYV+bRt/bt5O 6SfitF4XZvewKafZ8YaxPklhdloWe+I= -----END PRIVATE KEY-----
JwtSigner1.java代码
public class JwtSigner1 { private static final long EXPIRY_DURATION = 1000L * 60 * 60 * 1; // 1 hour public static void main(String[] args) throws Exception { File file = new File("mysecret1.pem"); System.out.println(JwtSigner1.generateTokenUsingPemFile(file)); } public static String generateTokenUsingPemFile(File file) throws Exception { Security.addProvider(new BouncyCastleProvider()); String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); String privateKeyPEM = key.replace("-----BEGIN PRIVATE KEY-----", "").replaceAll(System.lineSeparator(), "") .replace("-----END PRIVATE KEY-----", ""); byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); Map<String, Object> claims = new HashMap<>(); claims.put("data", " app"); claims.put("namespace", "sample"); return Jwts.builder().setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + EXPIRY_DURATION)) .setIssuedAt(new Date(System.currentTimeMillis())).signWith(privateKey).compact(); } }
异常信息
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : version mismatch: (supported: 00, parsed: 01 at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217) at java.security.KeyFactory.generatePrivate(KeyFactory.java:372) at c.s.v.d.w.c.util.JwtSigner1.generateTokenUsingPemFile(JwtSigner1.java:41) at c.s.v.d.w.c.util.JwtSigner1.main(JwtSigner1.java:28) Caused by: java.security.InvalidKeyException: IOException : version mismatch: (supported: 00, parsed: 01 at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:351) at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:356) at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:91) at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75) at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:316) at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:213) ... 3 more
解决方案
错误原因分析
报错中的版本不匹配说明:
- Java默认
RSAKeyFactory仅支持PKCS#8格式的私钥(版本号00) - 给定的PEM文件虽然头标注为
BEGIN PRIVATE KEY(PKCS#8标准头),但实际内容是PKCS#1格式的RSA私钥(版本号01),因此直接用PKCS8EncodedKeySpec解析会失败。
修正后的代码(方案一:手动解析ASN.1结构)
import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Security; import java.security.spec.RSAPrivateKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Base64; import io.jsonwebtoken.Jwts; public class JwtSigner1 { private static final long EXPIRY_DURATION = 1000L * 60 * 60 * 1; // 1 hour public static void main(String[] args) throws Exception { File file = new File("mysecret1.pem"); System.out.println(JwtSigner1.generateTokenUsingPemFile(file)); } public static String generateTokenUsingPemFile(File file) throws Exception { Security.addProvider(new BouncyCastleProvider()); String key = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); // 清理PEM内容:移除头尾标记及所有空白字符 String privateKeyPEM = key.replaceAll("-----BEGIN PRIVATE KEY-----", "") .replaceAll("-----END PRIVATE KEY-----", "") .replaceAll("\\s+", ""); byte[] encoded = Base64.getDecoder().decode(privateKeyPEM); // 解析PKCS#1格式的RSA私钥 try (ASN1InputStream asn1InputStream = new ASN1InputStream(encoded)) { ASN1Primitive primitive = asn1InputStream.readObject(); RSAPrivateKeyStructure rsaPrivateKey = new RSAPrivateKeyStructure((ASN1Sequence) primitive); RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent()); KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); PrivateKey privateKey = keyFactory.generatePrivate(rsaPrivateKeySpec); Map<String, Object> claims = new HashMap<>(); claims.put("data", " app"); claims.put("namespace", "sample"); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + EXPIRY_DURATION)) .setIssuedAt(new Date(System.currentTimeMillis())) .signWith(privateKey) .compact(); } } }
简化方案(方案二:使用BouncyCastle的PEMParser)
借助BouncyCastle的PEMParser可以自动处理PEM格式解析,无需手动清理Base64内容:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import java.io.File; import java.io.FileReader; import java.security.PrivateKey; import java.security.Security; import java.util.Date; import java.util.HashMap; import java.util.Map; import io.jsonwebtoken.Jwts; public class JwtSigner1 { private static final long EXPIRY_DURATION = 1000L * 60 * 60 * 1; // 1 hour public static void main(String[] args) throws Exception { File file = new File("mysecret1.pem"); System.out.println(JwtSigner1.generateTokenUsingPemFile(file)); } public static String generateTokenUsingPemFile(File file) throws Exception { Security.addProvider(new BouncyCastleProvider()); // 使用PEMParser读取并解析私钥 try (PEMParser pemParser = new PEMParser(new FileReader(file))) { Object object = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); PrivateKey privateKey = converter.getPrivateKey((org.bouncycastle.asn1.pkcs.PrivateKeyInfo) object); Map<String, Object> claims = new HashMap<>(); claims.put("data", " app"); claims.put("namespace", "sample"); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + EXPIRY_DURATION)) .setIssuedAt(new Date(System.currentTimeMillis())) .signWith(privateKey) .compact(); } } }
依赖注意事项
确保项目中引入BouncyCastle的相关依赖(以Maven为例):
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> </dependency>
内容的提问来源于stack exchange,提问作者Varun Ramachandran
相关产品推荐
相关产品推荐

