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).

Reply via email to