ADDITIONAL SYSTEM INFORMATION :
macOS 10.15.4
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment (build 11.0.7+10)
OpenJDK 64-Bit Server VM (build 11.0.7+10, mixed mode)
A DESCRIPTION OF THE PROBLEM :
When decoding a PKCS#8 encrypted private key using PBKDF2WithHmacSHA256 as the KDF, Java 11.0.7 incorrectly decodes it as PBKDF2WithHmacSHA1 and therefore fails to decrypt the key. This is fixed in Java 12 and later as a side-effect of https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8076190.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Generate a password encrypted PEM private key with OpenSSL 1.1.1 and the default encryption parameters. I converted an RSA ssh key using:
/usr/local/Cellar/openssl@1.1/1.1.1g/bin/openssl pkcs8 -topk8 -in ssh_ecdsa_key_pw -out openssl_ecdsa_key_pkcs8_v2.pem
The resulting PEM file data is included in the test case attached to this ticket. The default algorithm is PBKDF2 with HMAC-SHA256 and 2048 iterations then AES-CBC.
2. Decode the data from the PEM file using var keyInfo = new EncryptedPrivateKeyInfo(data)
3. Obtain a correct Cipher instance based on the AlgorithmParameters (see source code of attached test case for details)
4. Attempt to decrypt the private key using keyInfo.getKeySpec(cipher)
NOTE: there is probably a distinct bug here in all versions of Java in that keyInfo.getKeySpec(decryptionKey) always fails because it is unable to find the PBES2 cipher by OID. The workaround is to lookup the Cipher manually using keySpec.getAlgParameters().toString() which seems fragile.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The private key is correctly decrypted and a PKCS8EncodedKeySpec is returned.
ACTUAL -
Exception in thread "main" java.security.spec.InvalidKeySpecException: Cannot retrieve the PKCS8EncodedKeySpec
at java.base/javax.crypto.EncryptedPrivateKeyInfo.getKeySpec(EncryptedPrivateKeyInfo.java:255)
at test.main(test.java:22)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.PBES2Core.engineDoFinal(PBES2Core.java:323)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at java.base/javax.crypto.EncryptedPrivateKeyInfo.getKeySpec(EncryptedPrivateKeyInfo.java:250)
This is due to the wrong PBKDF2 algorithm being selected (SHA1 vs SHA-256).
---------- BEGIN SOURCE ----------
import static java.nio.charset.StandardCharsets.UTF_8;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
public class test {
public static void main(String... args) throws Exception {
EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(getKeyData());
Key decryptionKey = new SecretKeySpec("changeit".getBytes(UTF_8), "PBE");
// NB: keyInfo.getKeySpec(decryptionKey) results in:
// Exception in thread "main" java.security.NoSuchAlgorithmException: Cannot find any provider supporting 1.2.840.113549.1.5.13
// This is probably also a bug/RFE...
// Workaround is to use the toString() method on the algorithm parameters
AlgorithmParameters params = keyInfo.getAlgParameters();
Cipher cipher = Cipher.getInstance(params.toString());
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
// Works in Java 12+, BadPaddingException in Java 11:
keyInfo.getKeySpec(cipher);
}
private static final Base64.Decoder DECODER = Base64.getDecoder();
private static byte[] getKeyData() {
String pem =
"MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIgirdJtYu1XACAggA" +
"MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAWjKvvq4ZyQFPGI2tfmtrdBIIE" +
"0OjhVdYFD3Uw5Mj5JyhiiQCGrQ3/JBt1iNrJ38SzvSg7SCKbU94XRGlU6OsmufFi" +
"53mZOAbD3jyr4zM7kPc7Zh9BsdwXCkfxzzI6gokNDFeZ6EzHHUMBOwDUcRmzOShc" +
"tYytz5x9NGnxlA6OAyXHUXi5H6/9wqIMDNKGWPzNCXBcB3xfMQ8dy92NbK4ksZG2" +
"6TSXQG+Kqd4wRxZUm6AeTl28TBcXfsl7lavSP3m78+6k448TdkkI3W/AAK/81YBk" +
"uyhicCHXAbmoDf1ZHScVk54SuBOInRgp1pTGvjqCDw0bLWSWTdcHveQYlPac0S5Z" +
"jFRJDopN80plb/DFLVEgP3KRuIJFAHwzy6+0vKLxWVJJ/EBN8HYeaAVoyiV2zSX8" +
"F8wqIJt6o9vD4suBbUhPTygqIKHjU+wLNFEoLDwVPuUmjStH1uwaPvuFj0rtmT/7" +
"ef3aXJGU8hlt/cU7DrPMRn9qON+CLyzNI3CEFSqSfEdZ33DYjJp7EmXnCLHTEKpm" +
"o5U9dViAQKPCaJGEvuORxVi0+u0N8ake5DceyFojiPk6Ccd96Jvl2tkVKcJu4EpB" +
"0XGho+J2xnMWIIJ3Vc2K9dCMxSy+GonO0wHWVVnd2ye24I3Fhbd9mtVGuHnTrbAa" +
"9VH1MhcnQu2x/TVpt9wpTqhoMyufzOniBNoQRnwCHXy9MJrlDamXbBISO7SxaQPl" +
"jnkDPK6uGA6Ee+0GC8oE9drhrVlOgRbW29edRVpuag63q04yPT4Wn8sSMcR+Af9s" +
"jcgnzsDZrZ0iu66zmDaPhWBRX+dOLJWedOnBGGtD+en3117+GjRb7fw5Dz2QIuDU" +
"nM9oAenEk83zWW2cZd7ocU/8U0eZ66PGRgEzhfiITvXKw1gPxUD/CGLoNmmQJhhP" +
"QocdfGzyz1aoCut+wlD43lAoJOBDx/vYN6WaboeDq7Q3B/x/vhfRQxrMdUXfBt4Y" +
"ayJS6qgMeapHyiVGLbLxbxG1KSa7jSa1PIxoJuP0C0cGs+b8vpGlHbuy8HkepToM" +
"57zZxRJy7e9BEBTV0lJwygUvrqKjV8Mx6zszBnuXu8myC5GWZz6ttyC9mvAWqCdz" +
"DWrMeNwOIb88wl5XVYtVHN4BLDXE5uq9WKr2vSyy1MBFJ3Kj+0Qhq+w8NwNgFO73" +
"tEnbMb06h3C5oxTKdT3R8SzXyEqhZKZax1sEIE/w4fK6O/WoYzCRQX/WyaQJ1aKc" +
"N5KwZnF8f7aDZf58mMRMnvILnRHFHU5v0iYRGkxC7TMPJnUc5JpCkw7B+Qe6ItJu" +
"AxD4VrWNMvnT20TGp2b3MebrFn0sYvP5KG/wH1ipr3HkQo0e1gOnBDWKs1OQu4sP" +
"qYO2r6m33gVFgQeW/qhmw7A0datAcNTo45Hy80sFtCWzlu+Te6avCy6h8ErBi2dM" +
"JxtdNsqGjTrOgxLpALp/XDR0cUhWV7eJ+aKxKB4uYS4oSVHyrZQ5y2q5Ap6hwUUb" +
"8EOEEDzFcl+dzNR2GDUK4V6rVqsM6XX4SadtTOO2GDVck8F1i9z6cqKciH5VkNVZ" +
"AgciRt6oO1O3xkwOy2rpoLsy6AYseRgqkYsKcSE79bAcg1lavNlpin3kS4QfebFw" +
"/iI8DQfRtmesl0jljm3CPV3Hq/9i1nwX5c5oKiE1L95D";
return DECODER.decode(pem);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use Java 12 or later.
FREQUENCY : always