You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中原生读取OpenSSH或OpenSSL私钥?

解决Java原生读取OpenSSL传统私钥(非PKCS8)的问题

嘿,我太懂你这种找解决方案找得抓心挠肝的感觉了!你遇到的这种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一致:

  1. 提取并解码PEM中的Base64内容
  2. 解析SEC1的ASN.1结构,提取私钥字节和曲线参数
  3. 构造ECPrivateKeySpec,通过KeyFactory.getInstance("EC")生成ECPrivateKey

替代方案:手动打包为PKCS8格式

如果担心内部API的兼容性,还可以手动把PKCS#1的字节包装成PKCS8的ASN.1序列(PKCS8是将算法标识和原始私钥字节打包的通用格式),然后用PKCS8EncodedKeySpec直接生成私钥,这样就能完全使用标准API,无需依赖内部ASN.1工具类。

总之,你绝对不是唯一一个遇到这个问题的人——很多开发者都被Java标准库对传统OpenSSL私钥的支持不足坑过,上面的方案应该能帮你顺利解决问题!

内容的提问来源于stack exchange,提问作者Jonathan S. Fisher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:25:47