PKCS8 with PBES2 protection supported by EncryptedPrivateKeyInfo?

Bernd ecki at zusammenkunft.net
Sun Aug 7 19:18:16 UTC 2022


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 at 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

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).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/security-dev/attachments/20220807/09a08f2b/attachment.htm>


More information about the security-dev mailing list