如何更改Java+BouncyCastle写入私钥的格式?

问题描述 投票:0回答:1

我有一个 pkcs12 文件,其中包含我使用 openssl 创建的公钥和私钥对。我的Java程序读取pcks12文件并分别保存公钥和私钥以供http守护进程使用。然而,有些按键会导致问题。

当我使用 RSA 创建密钥时,一切正常。问题在于 EC,特别是 Java 和 Bouncy Castle。我可以在命令行上从 pkcs12 文件中提取私钥,并得到一个以“-----BEGIN PRIVATE KEY-----”开头的文件。我可以使用

openssl ec
命令读取该文件,HTTP 守护进程也接受它。

但是 Java 生成的密钥以“-----BEGIN EC PRIVATE KEY-----”开头。

openssl ec
命令无法读取该内容,并且 HTTP 守护进程也会拒绝它。

我需要能够导出私钥,以便 HTTP 可以使用它。我猜这意味着一个具有“-----BEGIN PRIVATE KEY-----”形式的密钥。否则,我需要能够让 apache httpd 守护进程接受 Java 输出的形式。

详细信息如下。非常感谢任何帮助!

这是我创建私钥的方法:

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 \
        -pkeyopt ec_param_enc:named_curve \
        -out "$KEY_DIR/$commonname.key" -pass pass:$PASSWORD

以下是我在命令行上从 PKCS12 文件中提取它的方法:

openssl pkcs12 -in rick5.p12 -nocerts -noenc -out good.key

这是我用来编写私钥的 Java 代码的要点(

certfile
是 PKCS12 文件):

try (FileInputStream in = new FileInputStream(certFile)) {
    
    KeyStore certs = KeyStore.getInstance(keyStoreType);
    certs.load(in, certSecret_in.toCharArray());

} catch(Exception e){
    throw new RuntimeException("Unable to load server certificate", e);
}           

try (PEMWriter writer = new PEMWriter(new FileWriter(path))){
            
    Key key = certificate.getKey(alias, secret.toCharArray());
    writer.writeObject(key);
            
} catch (Exception e) {
    throw new RuntimeException("Could not save private key", e);
}

这里有一些其他可能有用的信息。在下面的输出中,“good.key”是我使用 openssl 命令行从 pkcs12 文件中提取的密钥。 “bad.key”文件是 Java 生成的文件。

root@rick5:lumeta# openssl ec -in good.key -noout -text
read EC key
Private-Key: (384 bit)
priv:
ac:76:0c:6a:08:9f:53:a1:7d:0e:a9:be:35:87:cc:
aa:a2:ce:05:86:2c:9d:41:df:fe:0e:d0:4d:07:a2:
bf:b2:6b:19:0b:09:3b:68:2a:b4:93:d6:c5:83:cf:
dd:c2:97
pub:
04:e9:77:06:a0:a4:72:47:e3:9a:1a:2b:ff:cc:54:
c7:85:a4:2b:2a:c3:ce:2c:8c:17:7e:18:8b:a1:e2:
d8:95:5f:22:84:eb:44:63:51:1e:f5:20:43:5e:4e:
87:32:30:2a:41:18:82:77:91:e0:a7:3a:89:24:26:
87:67:3d:a8:4d:0f:b4:27:65:21:40:86:c7:c5:32:
cc:e8:fd:7d:11:8d:bb:48:dc:f9:af:aa:4c:d7:cd:
7f:b6:94:5d:89:1c:e4
ASN1 OID: secp384r1
NIST CURVE: P-384

root@rick5:lumeta# openssl ec -in bad.key -noout -text
read EC key
Could not read private key from bad.key
402CCD163B7F0000:error:1608010C:STORE routines:ossl_store_handle_load_result:unsupported:crypto/store/store_result.c:151:
unable to load Key

root@rick5:lumeta#  openssl asn1parse -in good.key -strictpem
0:d=0  hl=3 l= 182 cons: SEQUENCE
3:d=1  hl=2 l=   1 prim: INTEGER           :00
6:d=1  hl=2 l=  16 cons: SEQUENCE
8:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
17:d=2  hl=2 l=   5 prim: OBJECT            :secp384r1
24:d=1  hl=3 l= 158 prim: OCTET STRING      [HEX DUMP]:30819B0201010430AC760C6A089F53A17D0EA9BE3587CCAAA2CE05862C9D41DFFE0ED04D07A2BFB26B190B093B682AB493D6C583CFDDC297A16403620004E97706A0A47247E39A1A2BFFCC54C785A42B2AC3CE2C8C177E188BA1E2D8955F2284EB4463511EF520435E4E8732302A4118827791E0A73A89242687673DA84D0FB42765214086C7C532CCE8FD7D118DBB48DCF9AFAA4CD7CD7FB6945D891CE4

root@rick5:lumeta# openssl asn1parse -in bad.key -strictpem
0:d=0  hl=3 l= 155 cons: SEQUENCE
3:d=1  hl=2 l=   1 prim: INTEGER           :01
6:d=1  hl=2 l=  48 prim: OCTET STRING      [HEX DUMP]:AC760C6A089F53A17D0EA9BE3587CCAAA2CE05862C9D41DFFE0ED04D07A2BFB26B190B093B682AB493D6C583CFDDC297
56:d=1  hl=2 l= 100 cons: cont [ 1 ]
58:d=2  hl=2 l=  98 prim: BIT STRING
java openssl bouncycastle elliptic-curve
1个回答
0
投票

首先顺便说一句:

org.bouncycastle.openssl.PEMWriter
已被弃用大约十年,并在功能上被
org.bouncycastle.openssl.jcajce.JcaPEMWriter
取代。然而
PEMWriter
已被回溯为使用
JcaMiscPEMGenerator
JcaPEMWriter
相同的方式,因此实际上适用于您的情况,尽管这不是最佳实践。

Apache httpd 可以使用 PKCS8 形式的私钥(BEGIN/END [ENCRYPTED] PRIVATE KEY)或“旧版”SEC1 形式的私钥(BEGIN/END EC 私钥,带或不带 1421 式加密),但后者仅在以下情况下它的结构正确,但您使用的组合使其不正确。具体来说,Oracle/OpenJDK 提供程序 (SunEC) 将私钥编码为 PKCS8(所有 JCA 的标准),对于 EC,它在 AlgorithmIdentifier 部分中指定曲线,不需要也不会在每个算法中重新指定它部分。但是 Bouncy 的

MiscPEMGenerator
(由
Jca
子类继承)仅写入每个算法 = SEC1 部分,可能是因为 Bouncy 键实现返回 PKCS8,并且曲线在两个地方。因此,您的代码会生成一个 SEC1 格式的密钥,该密钥无法识别曲线,这是无法使用的。

如果您的 PKCS12 是使用私钥和证书的“友好名称”创建的,您没有显示或说出,但您确实使用了变量

alias
而不是硬编码
"1"
,这让我希望如此是这种情况(并且您对文件和密钥使用明显不同的密码,这意味着您没有使用 OpenSSL 创建它)您可以简单地使用 KeyStore 类型 PKCS12 来自 Bouncy 并且您的代码可以运行。

    KeyStore ks = KeyStore.getInstance("PKCS12","BC");
    try(InputStream is = new FileInputStream(file)){ ks.load(is,pass); }
    PrivateKey k1 = (PrivateKey)ks.getKey(alias, pass);
    PEMWriter p1 = new PEMWriter(new OutputStreamWriter(new FileOutputStream("79269719.1")));
    p1.writeObject(k1); p1.close();

或者,使用任何提供程序,您都可以手动构造一个包括曲线的 SEC1,其方式与我在 Java Bouncy Castle 生成的 ES256 密钥不适用于 JWT.io 中所做的相反(需要将其删除) ):

    // given PrivateKey k1 containing an Oracle/OpenJDK(SunEC) key
    // manually insert params to SEC1
    PrivateKeyInfo p8 = PrivateKeyInfo.getInstance(k1.getEncoded());
    // ECPrivateKey is org.bouncycastle.asn1.sec not java.security.interfaces or org.bouncycastle.jce.interfaces
    ECPrivateKey sec = ECPrivateKey.getInstance(p8.parsePrivateKey());
    // can retain sec.getPublicKey() in second argument if desired, but OpenSSL/Apache doesn't need it
    sec = new ECPrivateKey(256,sec.getKey(),null,p8.getPrivateKeyAlgorithm().getParameters());
    p1 = new PEMWriter(new OutputStreamWriter(new FileOutputStream("79269719.2")));
    p1.writeObject(new PemObject("EC PRIVATE KEY", sec.getEncoded())); p1.close();

另一方面,如果您愿意,可以使用更标准/可移植的 PKCS8 两种方式之一:

    // or generate PKCS8
    p1 = new PEMWriter(new OutputStreamWriter(new FileOutputStream("79269719.3")));
    p1.writeObject(new JcaPKCS8Generator(k1,null)); p1.close();
    // or manually
    p1 = new PEMWriter(new OutputStreamWriter(new FileOutputStream("79269719.4")));
    p1.writeObject(new PemObject("PRIVATE KEY", k1.getEncoded())); p1.close();

前者,

JcaPKCS8Generator
,也可以通过使用构造函数的第二个参数来生成加密 PKCS8,但是获取 Apache 的密码来解密私钥通常很痛苦。

© www.soinside.com 2019 - 2024. All rights reserved.