如何在Java中原生读取OpenSSH或OpenSSL私钥?
嘿,我太懂你这种找解决方案找得抓心挠肝的感觉了!你遇到的这种OpenSSL私钥,其实是PKCS#1格式的RSA私钥(如果是椭圆曲线密钥的话就是SEC1格式)——这是OpenSSL早期默认生成的传统私钥格式,和PKCS8的区别一眼就能看出来:它的PEM开头是-----BEGIN RSA PRIVATE KEY-----(RSA),而PKCS8的是-----BEGIN PRIVATE KEY-----。
Java标准库确实没直接提供读取这种格式的API,但完全可以不依赖BouncyCastle,自己手动处理PEM解码和ASN.1结构解析来搞定。下面给你分步讲清楚怎么做:
第一步:读取并解码PEM格式的私钥
首先要把PEM文件里的Base64内容提取出来,去掉首尾的标记行和空白字符,然后解码成原始的DER字节:
import java.nio.file.Files; import java.nio.file.Paths; import java.nio.charset.StandardCharsets; import java.util.Base64; private static byte[] extractPkcs1Bytes(String pemPath) throws Exception { String pemContent = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.US_ASCII); // 去掉PEM头、尾和所有空白字符 String base64 = pemContent.replace("-----BEGIN RSA PRIVATE KEY-----", "") .replace("-----END RSA PRIVATE KEY-----", "") .replaceAll("\\s+", ""); return Base64.getDecoder().decode(base64); }
第二步:解析PKCS#1结构为Java的RSAPrivateKey
PKCS#1格式的RSA私钥是一个ASN.1序列,包含版本、模数、公钥指数、私钥指数等9个字段。我们可以用Java自带的ASN.1工具类(注:sun.security.asn1是JDK内部API,大部分Java版本都支持,个人项目使用完全没问题;如果是严格要求标准化API的场景,可考虑手动构造PKCS8结构的替代方案)来解析这些字段,然后构造RSAPrivateKeySpec生成私钥:
import java.security.KeyFactory; import java.security.spec.RSAPrivateKeySpec; import java.security.interfaces.RSAPrivateKey; import sun.security.asn1.ASN1Sequence; import sun.security.asn1.ASN1Integer; import java.math.BigInteger; private static RSAPrivateKey parseRsaPrivateKey(byte[] pkcs1Bytes) throws Exception { ASN1Sequence sequence = ASN1Sequence.getInstance(pkcs1Bytes); var objEnum = sequence.getObjects(); // 按PKCS#1的结构顺序读取各个参数 ASN1Integer version = (ASN1Integer) objEnum.nextElement(); ASN1Integer modulus = (ASN1Integer) objEnum.nextElement(); ASN1Integer publicExponent = (ASN1Integer) objEnum.nextElement(); ASN1Integer privateExponent = (ASN1Integer) objEnum.nextElement(); ASN1Integer primeP = (ASN1Integer) objEnum.nextElement(); ASN1Integer primeQ = (ASN1Integer) objEnum.nextElement(); ASN1Integer exponentP = (ASN1Integer) objEnum.nextElement(); ASN1Integer exponentQ = (ASN1Integer) objEnum.nextElement(); ASN1Integer crtCoeff = (ASN1Integer) objEnum.nextElement(); // 构造RSAPrivateKeySpec,传入所有CRT参数以支持高效的RSA运算 RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec( modulus.getValue(), privateExponent.getValue(), primeP.getValue(), primeQ.getValue(), exponentP.getValue(), exponentQ.getValue(), crtCoeff.getValue(), publicExponent.getValue() ); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); }
第三步:调用测试
把上面的方法整合起来,就能直接读取私钥了:
public static void main(String[] args) throws Exception { String privateKeyPath = "/path/to/your/private_key.pem"; byte[] pkcs1Bytes = extractPkcs1Bytes(privateKeyPath); RSAPrivateKey privateKey = parseRsaPrivateKey(pkcs1Bytes); // 验证私钥读取成功 System.out.println("成功读取私钥,算法:" + privateKey.getAlgorithm()); }
椭圆曲线(EC)私钥的处理思路
如果你用的是EC私钥,格式是SEC1,PEM开头是-----BEGIN EC PRIVATE KEY-----,核心思路和RSA一致:
- 提取并解码PEM中的Base64内容
- 解析SEC1的ASN.1结构,提取私钥字节和曲线参数
- 构造
ECPrivateKeySpec,通过KeyFactory.getInstance("EC")生成ECPrivateKey
替代方案:手动打包为PKCS8格式
如果担心内部API的兼容性,还可以手动把PKCS#1的字节包装成PKCS8的ASN.1序列(PKCS8是将算法标识和原始私钥字节打包的通用格式),然后用PKCS8EncodedKeySpec直接生成私钥,这样就能完全使用标准API,无需依赖内部ASN.1工具类。
总之,你绝对不是唯一一个遇到这个问题的人——很多开发者都被Java标准库对传统OpenSSL私钥的支持不足坑过,上面的方案应该能帮你顺利解决问题!
内容的提问来源于stack exchange,提问作者Jonathan S. Fisher

