通过 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
尝试过的选项:
- 确保在容器中 运行正在读取密钥文件。记录器“InputStreamExists --> {}”给出字节数
- 运行 key.k8 上的 dos2unix 只是为了确保没有 Window 的“^M”字符可能导致问题,因为容器是 linux 一个:来自 adoptopenjdk/openjdk11-openj9:最新
不确定我做错了什么,但如有任何帮助或指点,我们将不胜感激。
编辑:
转念一想,在创建 JceOpenSSLPKCS8DecryptorProviderBuilder
时,您并未明确指定提供商:
new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME) // add this line
.build(passphrase.toCharArray());
OpenJ9 似乎使用了不同的 provider/algo 选择机制,默认选择 SunJCE
的 AESCipher
class 作为 CipherSpi,而 Hotspot 选择 BouncyCastleProvider
的AES
class.
明确指定供应商应该适用于所有情况。
或者,在添加 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 中所述。所以也许依靠它不是一个好主意。
我正在尝试读取如下所示的 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
尝试过的选项:
- 确保在容器中 运行正在读取密钥文件。记录器“InputStreamExists --> {}”给出字节数
- 运行 key.k8 上的 dos2unix 只是为了确保没有 Window 的“^M”字符可能导致问题,因为容器是 linux 一个:来自 adoptopenjdk/openjdk11-openj9:最新
不确定我做错了什么,但如有任何帮助或指点,我们将不胜感激。
编辑:
转念一想,在创建 JceOpenSSLPKCS8DecryptorProviderBuilder
时,您并未明确指定提供商:
new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME) // add this line
.build(passphrase.toCharArray());
OpenJ9 似乎使用了不同的 provider/algo 选择机制,默认选择 SunJCE
的 AESCipher
class 作为 CipherSpi,而 Hotspot 选择 BouncyCastleProvider
的AES
class.
明确指定供应商应该适用于所有情况。
或者,在添加 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 中所述。所以也许依靠它不是一个好主意。