Search code examples
javaencryptionkeystorepkcs#12p12

How to create a PKCS12 keystore with privatekey encryption PBEWithHmacSHA512AndAES_128


I need to build a PKCS-12 file in Scala/Java and I wish to use an AES based encryption of the private-key (e.g. PBEWithHmacSHA512AndAES_128)

I use this code (taken from here)

val outputStream = new FileOutputStream(file)
val salt = new Array[Byte](20)
new SecureRandom().nextBytes(salt)
val kspkcs12 = KeyStore.getInstance("PKCS12")
kspkcs12.load(null, null)
kspkcs12.setEntry("test", new KeyStore.PrivateKeyEntry(keys.getPrivate, Array(cert)),
  new KeyStore.PasswordProtection("changeMe".toCharArray, "PBEWithHmacSHA512AndAES_128", new PBEParameterSpec(salt,
    100000)))
kspkcs12.store(outputStream, "changeMe".toArray)

now, when checking the result with

 openssl pkcs12 -info -in filename.p12  -noout

I'm getting:

MAC:sha1 Iteration 100000 PKCS7 Data Shrouded Keybag: PBES2<unsupported parameters> PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 50000 Certificate bag'

Why am I getting a "PKCS7 Encrypted data" instead of PKCS12? I understand from the

"PBES2<unsupported parameters>"

message that the provider does not support the requested algorithm. Is there a PKCS12 provider that does?


Solution

  • Why am I getting a "PKCS7 Encrypted data" instead of PKCS12?

    PKCS is a stack of specifications, i.e. each higher level spec re-uses lower-level specs.

    In case of PKCS#12, we encounter at least:

    • PKCS#12 (keystore format)
    • PKCS#7 (signed/encrypted message)
    • PKCS#5 (PBKDF2)
    • PKCS#9 (message digest)
    • PKCS#8 (keypair format)

    So it's normal that you see messages relating to PKCS#7 in the output.

    I understand from the "PBES2<unsupported parameters>" that the provider does not support the requested algorithm.

    Yes, it's a bug in Java. The built-in JCE provider erroneously encodes the PBES2 tag twice in the output stream:

    Once in AlgorithmId.derEncode()

    public void derEncode (OutputStream out) throws IOException {
        DerOutputStream bytes = new DerOutputStream();
        DerOutputStream tmp = new DerOutputStream();
    
        bytes.putOID(algid);                               // <<< Writes 1.2.840.113549.1.5.13
    

    And once in PBES2Parameters.engineGetEncoded():

    protected byte[] engineGetEncoded() throws IOException {
        DerOutputStream out = new DerOutputStream();
        DerOutputStream pBES2Algorithms = new DerOutputStream();
        pBES2Algorithms.putOID(pkcs5PBES2_OID);            // <<< Writes 1.2.840.113549.1.5.13 again
    

    Here's what it produces (I'm using HMAC SHA256 with AES256):

    $ openssl asn1parse -inform der -strparse 973 < test.p12
        0:d=0  hl=4 l=1441 cons: SEQUENCE          
        4:d=1  hl=4 l=1437 cons: SEQUENCE          
        8:d=2  hl=2 l=  11 prim: OBJECT            :pkcs8ShroudedKeyBag
       21:d=2  hl=4 l=1358 cons: cont [ 0 ]        
       25:d=3  hl=4 l=1354 cons: SEQUENCE          
       29:d=4  hl=2 l= 116 cons: SEQUENCE          
       31:d=5  hl=2 l=   9 prim: OBJECT            :PBES2
       42:d=5  hl=2 l= 103 cons: SEQUENCE          
       44:d=6  hl=2 l=   9 prim: OBJECT            :PBES2   <<<<< PBES2 tag encoded twice!!
       55:d=6  hl=2 l=  90 cons: SEQUENCE          
       57:d=7  hl=2 l=  57 cons: SEQUENCE          
       59:d=8  hl=2 l=   9 prim: OBJECT            :PBKDF2
       70:d=8  hl=2 l=  44 cons: SEQUENCE          
       72:d=9  hl=2 l=  20 prim: OCTET STRING      [HEX DUMP]:9F642532C15BBF1E566AA6429DA450EFBF0FF265
       94:d=9  hl=2 l=   3 prim: INTEGER           :0186A0
       99:d=9  hl=2 l=   1 prim: INTEGER           :20
      102:d=9  hl=2 l=  12 cons: SEQUENCE          
      104:d=10 hl=2 l=   8 prim: OBJECT            :hmacWithSHA256
      114:d=10 hl=2 l=   0 prim: NULL              
      116:d=7  hl=2 l=  29 cons: SEQUENCE          
      118:d=8  hl=2 l=   9 prim: OBJECT            :aes-256-cbc
      129:d=8  hl=2 l=  16 prim: OCTET STRING      [HEX DUMP]:DF2D490C6BA58A19EDAF8D22E2A2CFA0
      147:d=4  hl=4 l=1232 prim: OCTET STRING      [HEX DUMP]:22343E6418F8C60857D9A5CC089D...
    

    And here's how it should be (output from openssl pkcs12 -encode):

    $ openssl asn1parse -inform der -strparse 1003 < test.p12
        0:d=0  hl=4 l=1414 cons: SEQUENCE          
        4:d=1  hl=4 l=1410 cons: SEQUENCE          
        8:d=2  hl=2 l=  11 prim: OBJECT            :pkcs8ShroudedKeyBag
       21:d=2  hl=4 l=1329 cons: cont [ 0 ]        
       25:d=3  hl=4 l=1325 cons: SEQUENCE          
       29:d=4  hl=2 l=  87 cons: SEQUENCE          
       31:d=5  hl=2 l=   9 prim: OBJECT            :PBES2
       42:d=5  hl=2 l=  74 cons: SEQUENCE          
       44:d=6  hl=2 l=  41 cons: SEQUENCE          
       46:d=7  hl=2 l=   9 prim: OBJECT            :PBKDF2
       57:d=7  hl=2 l=  28 cons: SEQUENCE          
       59:d=8  hl=2 l=   8 prim: OCTET STRING      [HEX DUMP]:6FA108004C54EAC4
       69:d=8  hl=2 l=   2 prim: INTEGER           :0800
       73:d=8  hl=2 l=  12 cons: SEQUENCE          
       75:d=9  hl=2 l=   8 prim: OBJECT            :hmacWithSHA256
       85:d=9  hl=2 l=   0 prim: NULL              
       87:d=6  hl=2 l=  29 cons: SEQUENCE          
       89:d=7  hl=2 l=   9 prim: OBJECT            :aes-256-cbc
      100:d=7  hl=2 l=  16 prim: OCTET STRING      [HEX DUMP]:CDACD92F68D13672599CD034CF3E791A
      118:d=4  hl=4 l=1232 prim: OCTET STRING      [HEX DUMP]:44725D0E70327934F75AD51CA7E3...
    

    How it should be is specified in RFC 2898, Appendix A.4.

    Is there a PKCS12 provider that does?

    Yes, here's how to create a valid PKCS#12 keystore using BouncyCastle:

    Security.addProvider(new BouncyCastleProvider());
    File file = new File("test.p12");
    char[] password = "test".toCharArray();
    
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(2048);
    KeyPair keypair = keyGen.genKeyPair();
    X500Name issuer = new X500Name("CN=test");
    X500Name subject = new X500Name("CN=test");
    BigInteger serial = new BigInteger("1");
    Date notBefore = new Date();
    Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 3600000L);
    JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, keypair.getPublic());
    ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA").build(keypair.getPrivate());
    certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, new BasicConstraints(true));
    X509Certificate cert1 = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer));
    
    OutputEncryptor pkenc = new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC)
        .setPRF(PBKDF2Config.PRF_SHA256).setIterationCount(100000).setProvider("BC").build(password);
    
    JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
    PKCS12SafeBagBuilder certBagBuilder = new JcaPKCS12SafeBagBuilder(cert1);
    
    certBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("test"));
    SubjectKeyIdentifier pubKeyId = extUtils.createSubjectKeyIdentifier(cert1.getPublicKey());
    certBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);
    
    PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(keypair.getPrivate(), pkenc);
    
    keyBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("test"));
    keyBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);
    
    PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();
    
    builder.addData(keyBagBuilder.build());
    
    OutputEncryptor crtenc = new JcePKCSPBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC)
        .setIterationCount(50000).setProvider("BC").build(password);
    
    builder.addEncryptedData(crtenc, new PKCS12SafeBag[]{certBagBuilder.build()});
    
    PKCS12PfxPdu pfx = builder.build(new JcePKCS12MacCalculatorBuilder(NISTObjectIdentifiers.id_sha256), password);
    
    try (FileOutputStream out = new FileOutputStream(file)) {
        out.write(pfx.getEncoded(ASN1Encoding.DL));
    }
    

    The result:

    $ openssl pkcs12 -info -nodes -in test3.p12 -passin pass:test -noout
    MAC:sha256 Iteration 1024
    PKCS7 Data
    Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 100000, PRF hmacWithSHA256
    PKCS7 Encrypted data: pbeWithSHA1And128BitRC2-CBC, Iteration 50000
    Certificate bag