通过 bouncycastle 读取 PKCS#8 格式的加密私钥,Java 在 docker 容器中失败

Reading encrypted private key in PKCS#8 format through bouncycastle, Java failing in docker container

我正在尝试读取如下所示的 PKCS#8 私钥:

key.k8 -->(示例密钥。密码 - 123456):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQILbKY9hPxYSoCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCvaGt2Hmm2NpHpxbLvHKyOBIIE
0IQ7dVrAGXLZl0exYIvyxLAu6zO00jL6b3sb/agTcCFOz8JU6fBanxY0d5aYO4Dn
mynQG7BoljU470s0zIwW/wk0MmdUFl4nXWBX/4qnG0sZqZ9KZ7I8R/WrBkmpX8C/
4pjdVhu8Ht8dfOYbkbjMBTohDJz8vJ0QwDIXi9yFjjef+QjwrFOl6kAeDJFVMGqc
s7K/wOnhsL1XxfW9uTulPiZh5YTZKcatMkeGDR7c+cg5I+Mutim92diWuCekhNoa
uvhUy1M3cbs7Azp1Mhz+V0CDKklI95EvN4u23WhiJPCjAofC/e45/heOP3Dwm7WZ
zHEY1C/X8PsTl6MEEIF3ZJP+4Vr0corAs1L2FqE6oOng8dFFYmF5eRyBx6bxFd05
iYbfOH24/b3qtFKPC689kGEd0gWp1dwES35SNNK+cJqVRTjgI0oKhOai3rhbGnmp
tx4+JqploQgTorj4w9asbtZ/qZA2mYSSR/Q64SHv7LfoUCI9bgx73MqRQBgvI5yS
b4BoFBnuEgOduZLaGKGjKVW3m5/q8oiDAaspcSLCJMIrdOTYWJB+7mfxX4Xy0vEe
5m2jXpSLQmrfjgpSTpHDKi/3b6OzKOcHjSFBf8IoiHuLc5DVvLECzDUxxaMrTZ71
0YXvEPwl2R9BzEANwwR9ghJvFg1Be/d5W/WA1Efe6cNQNBlmErxD6l+4KDUgGjTr
Aaksp9SZAv8uQAsg7C57NFHpTA5Hznr5JctL+WlO+Gk0cAV6i4Py3kA6EcfatsnS
PqP2KbxT+rb2ATMUZqgWc20QvDt6j0CTA1BuVD1PNhnAUFvb2ocyEEXOra22DPPS
UPu6jirSIyFcjqFjJ9A1FD9L4/UuX2UkDSLqblFlYB1+G55KZp+EKz8SZoN5qXy1
LyMtnacEP5OtRDrOjopzVNiuV1Uv63M9QVi1hZlVLJEomgjWuvuyEuIwDaY2uryW
vx+jJEZyySFkb1JwAbrm+p6sCTFnbQ/URKC2cit/FJyKqNim6VQvGL8Sez34qV3z
D13QJgTZfsy+BaZoaQ6cJTXtJ8cN0IcQciOiDNBKMW66zO6ujS8G+KNviNQypDm6
h4sOgjMqLaZ4ezPEdNj/gaxV7Y15nVRu0re8dVkaa5t9ft/sh6A+yeTD5tS5hHkf
NI7uJPTaTXVoz7xq2PAJUTWujMLMZKtmNOzNqYvxWRy3tCOFobBQkMxqEBEwHd+x
SA+gFcJKJ+aNfCGZJ5fFr8rNlhtOF6uMwOAlfiUlP/pCUDUCKPjZVj4K95yNc8Io
jSZSPb5tGPe0HqXgc6IAfQarlUZt90oVtzL0OfOfTxe1bEzS2ccNadbx/6vjLBc4
q5UuUBppl3rXpbuZ7J1Rp3n2byF4APxFdT2LHKq+MYMfWUToau/TCMT4lFIM9tM8
7TuuyUT2PKzf/xlsl4iScw96z9xxGPQrXn7IA2W5iL+0eCLztJdjNRX1FisdfIBL
PraOVlmF8jHKbFdRZ8Yi8pApbQjvHi24g7dX7u/cq1FH/VE+nJ0O8YVCYVDw13CW
h0p7yD7BuB0R+0WnR0yvkp30vK4/rtCB+Ob8bH/+HvAZrAU5X8jq/wsQbLkrLHZV
6A6GGfX8+hy5AoaXsH1BHnMyXkaF6Mv29z8JcslDJxX/
-----END ENCRYPTED PRIVATE KEY-----

正在使用以下代码解析私钥:

 InputStream privateKeyInputStream = getPrivateKeyInputStream(); // reads the key file from classpath and share as DataStream
 logger.info("InputStreamExists --> {} ", privateKeyInputStream.available());
 PEMParser pemParser = new PEMParser(new InputStreamReader(privateKeyInputStream));
 Object pemObject = pemParser.readObject();
 if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
     // Handle the case where the private key is encrypted.
     PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) pemObject;
     InputDecryptorProvider pkcs8Prov =
            new JceOpenSSLPKCS8DecryptorProviderBuilder().build(passphrase.toCharArray());
     privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(pkcs8Prov); // fails here
}

InputStream resourceAsStream = null;
    if ("local".equals(privateKeyMode)) {
      resourceAsStream = this.getClass().getResourceAsStream(privateKeyPath);
    } else {
      File keyFile = new File(privateKeyPath);
      logger.info(
          "Key file found in {} mode. FileName : {}, Exists : {}",
          privateKeyMode,
          keyFile.getName(),
          keyFile.exists());
      try {
        resourceAsStream = new DataInputStream(new FileInputStream(keyFile));
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      }

当我在 windows 上通过 intelliJ 运行 宁此代码时,代码工作正常但是当我 运行 它通过 docker 容器时,我得到以下异常:

org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: failed to construct sequence from byte[]: Extra data detected in stream
snowflake-report-sync    |      at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source) ~[bcpkix-jdk15on-1.64.jar!/:1.64.00.0]
snowflake-report-sync    |      at com.optum.snowflakereportsync.configuration.SnowFlakeConfig.getPrivateKey(SnowFlakeConfig.java:103) ~[classes!/:na]
snowflake-report-sync    |      at com.optum.snowflakereportsync.configuration.SnowFlakeConfig.getConnectionProperties(SnowFlakeConfig.java:67) ~[classes!/:na]

以下是使用的 Dockerfile:

FROM adoptopenjdk/openjdk11-openj9:latest
COPY build/libs/snowflake-report-sync-*.jar snowflake-report-sync.jar
RUN mkdir /encryption-keys
COPY encryption-keys/ /encryption-keys/ #keys are picked from docker filesystem when running in container
EXPOSE 8080
CMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar snowflake-report-sync.jar

尝试过的选项:

不确定我做错了什么,但如有任何帮助或指点,我们将不胜感激。

编辑:

转念一想,在创建 JceOpenSSLPKCS8DecryptorProviderBuilder 时,您并未明确指定提供商:

new JceOpenSSLPKCS8DecryptorProviderBuilder()
    .setProvider(BouncyCastleProvider.PROVIDER_NAME) // add this line
    .build(passphrase.toCharArray());

OpenJ9 似乎使用了不同的 provider/algo 选择机制,默认选择 SunJCEAESCipher class 作为 CipherSpi,而 Hotspot 选择 BouncyCastleProviderAESclass.

明确指定供应商应该适用于所有情况。

或者,在添加 BouncyCastleProvider 时,您可以将其插入到第一个首选位置(即 Security.insertProviderAt(new BouncyCastleProvider(), 1) 而不是 Security.addProvider(new BouncyCastleProvider())),以便它被选中。

(我仍然不清楚为什么不同 JVM 之间的提供者选择机制不同。)


原文post:

我已经成功地重现了这个问题,此时我认为这是与 OpenJ9 JVM 的不兼容问题。

改为从 Hotspot 基本图像开始,例如

FROM adoptopenjdk:11-jre-hotspot

使代码工作。

(尚不完全确定故障是否在于 Docker 图像本身、OpenJ9 JVM 或 BouncyCastle)

就像@Bragolgirith 怀疑的那样,BouncyCastle 似乎与 OpenJ9 有问题。我想这不是 Docker 问题,因为我也可以在 GitHub Actions 上重现它。它也不限于 BouncyCastle 1.64 或 1.70,它在两个版本中都有。它也发生在 Windows、MacOS 和 Linux 上的 OpenJ9 JDK 11、14、17,但对于 Java 和 [=51 的相同矩阵=] 适用于 Adopt-Hotspot 和祖鲁语的版本。

这是一个 example Maven project and a failed matrix build. So if you select another JVM type, you should be fine. I know that @Bragolgirith already suggested that, but I wanted to make the problem reproducible for everyone and also provide an MCVE,以防有人想打开 BC 或 OpenJ9 问题。

P.S.: 这也不是 InputStreamReader 的字符集问题。 This build fails exactly the same as before after I changed the constructor call.


更新: 我已经创建了 BC-Java issue #1099。让我们看看维护者对此有何评论。


更新 2: 您的问题的解决方案是明确地将安全提供程序设置为 BC 作为您的输入解密提供程序。感谢 David Hook for his helpful comment in #1099.

BouncyCastleProvider securityProvider = new BouncyCastleProvider();
Security.addProvider(securityProvider);

// (...)

InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder()
  // Explicitly setting security provider helps to avoid ambiguities
  // which otherwise can cause problems, e.g. on OpenJ9 JVMs
  .setProvider(securityProvider)
  .build(passphrase.toCharArray());

参见 this commit and the corresponding build,现在传递所有平台、Java 版本和 JVM 类型(包括 OpenJ9)。

因为@Bragolgirith 在他的回答中提到了它:如果你想避免显式 new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(securityProvider),在这种情况下调用 Security.insertProviderAt(securityProvider, 1) 而不是简单地 Security.addProvider(securityProvider) 也可以解决问题.但这只有在您的代码的其他部分或任何 third-party 库之后没有将另一个提供者设置为位置 1 时才成立,如 Javadoc 中所述。所以也许依靠它不是一个好主意。