Hi,
I did this not long ago. You are close, but you do not need to do the
Cipher.init() separately, it is done as part of
EncryptedPrivateKeyInfo.getKeySpec(). It also reads the encoding for
the count and iv.
The below will decrypt the DER-encoded 'data' into a
PKCS8EncryptedKeySpec for you to use.
EncryptedPrivateKeyInfo epki = new EncryptedPrivateKeyInfo(data);
PBEKeySpec pks = new PBEKeySpec(password);
SecretKeyFactory skf = SecretKeyFactory.getInstance(epki.getAlgName());
SecretKey sk = skf.generateSecret(pks);
PKCS8EncodedKeySpec keySpec = epki.getKeySpec(sk);
Tony
On 8/7/22 12:18 PM, Bernd wrote:
Hello,
there is a longstanding issue in the PostgreSQL JDBC driver which reads
secret keys in PKCS#8 format, but does not support the newer PKCS#5 2.0
(PBES2) modes (-v1 works). The (naive) code is here:
pgjdbc/LazyKeyManager.java at 80d4ed34c99d51dd8b06df00baad0265fd620fec ·
pgjdbc/pgjdbc · GitHub
<https://github.com/pgjdbc/pgjdbc/blob/80d4ed34c99d51dd8b06df00baad0265fd620fec/pgjdbc/src/main/java/org/postgresql/ssl/LazyKeyManager.java#L220>
I was playing around with EncryptedPrivateKeyInfo in order to see whats
needed to get it working with PBES2 encryption, but it did not work with
quite a few tries.
I wonder is the Code behind PBES2Parameters in JCE supposed to work
and interoperable with openssl PKCS#8? If I understand the api correctly
the following code should work, but it results in a padding error:
public static void main(String[] args) throws Throwable
{
byte[] b = readFileFully("test-key.p8"); // DER Format
EncryptedPrivateKeyInfo ePKInfo = new EncryptedPrivateKeyInfo(b);
System.out.println("en " + ePKInfo + " alg=" +
ePKInfo.getAlgName()
+ " p=" +
ePKInfo.getAlgParameters().toString());
AlgorithmParameters algParams = ePKInfo.getAlgParameters();
//PBEParameterSpec pbep =
algParams.getParameterSpec(PBEParameterSpec.class);
//System.out.println("pbep " + pbep);
//AlgorithmParameterSpec cp = pbep.getParameterSpec();
//System.out.println("cp = " + cp ); // IvParameters
PBEKeySpec pbeKeySpec = new PBEKeySpec("test".toCharArray());
//PBEKeySpec pbeKeySpec = new PBEKeySpec("test".toCharArray(),
//
pbep.getSalt(), pbep.getIterationCount(), 256);
SecretKeyFactory skFac =
SecretKeyFactory.getInstance(algParams.toString());
Key pbeKey = skFac.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance(algParams.toString());
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
// Decrypt the encrypted private key
// when passing pbeKey here instead of cipher the algParams are
lost
KeySpec pkcs8KeySpec = ePKInfo.getKeySpec(cipher); // L61 -->
throws
// not reached
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey key = kf.generatePrivate(pkcs8KeySpec);
System.out.println("k=" + key);
}
The test object created with openssl:
> openssl req -x509 -newkey rsa:2048 -keyout test-key.pem -out
test-cert.pem -sha256 -days 365 -nodes
> openssl pkcs8 -topk8 -in test-key.pem -out test-key.p8 -outform der
# password "test"
> openssl asn1parse -in test-key.p8 -inform DER
0:d=0 hl=4 l=1325 cons: SEQUENCE
4:d=1 hl=2 l= 87 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 74 cons: SEQUENCE
19:d=3 hl=2 l= 41 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 28 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:BDCF0B11DB8BAFAB
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=5 hl=2 l= 12 cons: SEQUENCE
50:d=6 hl=2 l= 8 prim: OBJECT :hmacWithSHA256
60:d=6 hl=2 l= 0 prim: NULL
62:d=3 hl=2 l= 29 cons: SEQUENCE
64:d=4 hl=2 l= 9 prim: OBJECT :aes-256-cbc
75:d=4 hl=2 l= 16 prim: OCTET STRING [HEX
DUMP]:E355E1C72C9F486DDBE2E1F58EE7B61E
93:d=1 hl=4 l=1232 prim: OCTET STRING [HEX
DUMP]:E4D9C29E4789840093EE371AB6EACB3111920E636CCB924651125D97CBD5312156214AD173CF4159AEEDBB497900815FE3B24184846CA252A421784CAD121B59C93EE3427BEA7CF0E404FAD0F226A2D13B86BD4898455B0750FBA6C4FB5F0B66980BD586CFFCBB5FA2170A67917FD55F7AF0E03D03078E5191965F74C099E357F28B6DA969E3CF3D713ABBC35C20A513E8068822A1C9B67BAFB650FB4B8EB7755A9F1760CD0B82AEBFBCCA01F575C377CD5AA2732D42F10A43EEF46048650E492F9FE1FD56596DDAC70461BF3E60CEA97F7EA99741254D1C1452CF1081DD799EACC74C8C0E806104230AE91E560C8F458B7BB358F031726355E99D31938CF39EC40B76D963FC3D45A59C7BE14CC769E7E4DA8C9FC08F4BD1A4C4CC07141BA0D5A31F0319E32D48A12A2BA4ABA4979A68447AFA57B8A9F82D465B1E765169B1C339C88F9EC9934B0B1B58C5793991FB9990F44C9C7A816ABE97015ECFB408CBF906BABAF9C7F5D0F32673AA1D9D72A0C3738FC9C1909FB24A029A3928C583B2DE4CCF3247F7C89D97E8DEEF3E08796789D43B56A66D1C07B3D368948964FAA1746EEC59605A14934B02BFE14B6BC8A281973E76215E7FF9A43FD33780F34D76A21791586EDBA5999BCE08D8DF5E20B736DDA9B91091991B0EE883CC3E8358D8403486B53D205C0DD33EE3224D1BE40DE9FCD444D70BDE6500B08FF843153EC8EF01F5CDC9D01CB9372BBCA6F42D5D13159AC9E67CAB075A20D86F9538AE604E87DEBF1300D3E6A8BEB72F1F2124F903E95A558DE3BEB61A1AAE792E9A77A6F860BD736D2C0BA97FDA25D4E3AEAF8149415E68E473F48D99860DC2A49F108AE5468DFC36A5764E3B06412D2C3498D87E9F5D487421FD5C15C0EA8306A69BFA2956D8F8C1F4CD4786EDE575515202F8442CBA2E3AA21CB267DDEC5BA7BB1CBC54F471E84CD522FCE9A69F084DD4EB100F00875A0A32BCCD6D5F5473FF8CA6E6B448BAB76A659E0350AAFC3A2EFBCCAF1AFB52F3C8A7F3B452E5B35CDC2CD05710D86529EE3544DE9AB5A7FAA9AD8ADC93B0F7F5432FF8EB280A3BEE3C094796C9938DC5A4956D47CE4CE7A7D2300E328D47F030332EC84296CD57F02B61B63A099210C78F64B3DB618E79A17585030CB82B8B39BB34333D148F5856D04B4083BD0F006B8A747D7E020DDD8DCA99AEF6B719367DB619C7736D7897F071875CC9C17F7AF3E742F06CDBEBECDF486CC6B143BB96FC4FE34A54CB1CBFDF877598094B459DBAA6B101B0DE8000A5E2D06ABAE1C651EDF3EA3A7D37EA263AC999B65F621E1F65FFD06F461B7285EB90AE2C32AA60C8424966BF37F2C4F502BFE253E252C1CD652FC8667E488BF8B282D0379B38AC9B53014C87C55E8DE8EB7A217F521F46FC13F983C3E77B61417A552D7ACC147FC40C295DD87DDC4972AF340384B449A8A888BD1F4AB7B5B2D6AB3E963F7E90870639709075FA4A618A640753B6E2B6B4C0734BFEEE7A14C94C64131C666749E813A07FB8622278BF7F823675883F8B57C9BC081420F96C7F71E006D3A41299711E6E283F7C97E1ECC1202D98DF0BB13742D8DB5FDFE4569C35E207983645AE86AA2286680A9EDB69742DBD164D499096C90A063EE6304C81D2DB8311A73F80D7E1BB14B2C0C249D14F7D953369513731A5DEDB2AAD671E74F704CAFBCF4CDEDB72E42A238DD9CA2AB77ABAE80350F508062276A66C9BF81C325368E496C9490B
From debugging it looks like it picked up the salt for PBKFD#2 and the
Iv for AES, but it is a bit nested, so I tried passing Cipher and/or Key
to getKeySpec().
The output
en javax.crypto.EncryptedPrivateKeyInfo@27716f4
alg=1.2.840.113549.1.5.13 p=PBEWithHmacSHA1AndAES_256
Exception in thread "main" java.security.spec.InvalidKeySpecException:
Cannot retrieve the PKCS8EncodedKeySpec
at
javax.crypto.EncryptedPrivateKeyInfo.getKeySpec(EncryptedPrivateKeyInfo.java:255)
at bernd.TestKey.main(TestKey.java:61)
Caused by: javax.crypto.BadPaddingException: Given final block not
properly padded. Such issues can arise if a bad key is used during
decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.PBES2Core.engineDoFinal(PBES2Core.java:323)
at javax.crypto.Cipher.doFinal(Cipher.java:2168)
at
javax.crypto.EncryptedPrivateKeyInfo.getKeySpec(EncryptedPrivateKeyInfo.java:250)
... 1 more
I see there are some test cases for a similar usage, but they depend on
an initialized key with defaults, so I am not sure if that would
actually use AES IV?
https://github.com/openjdk/jdk/blob/357f990e3244feaba6d8709b7ea50660220a418b/test/jdk/sun/security/x509/AlgorithmId/PBES2.java#L89
<https://github.com/openjdk/jdk/blob/357f990e3244feaba6d8709b7ea50660220a418b/test/jdk/sun/security/x509/AlgorithmId/PBES2.java#L89>
BTW: its hard to say if the problem is "only" caused by UTF-8 vs. UTF-16
password encoding. In that case it would be a bit unfortunate, is it
maybe an option to add that support? (however I am not sure what
password encoding openssl uses in this case).