Travis Griggs
Travis Griggs

Reputation: 22282

How do/can I generate a PKCS#12 file using python and the cryptography module?

How do/can I generate a PKCS#12 file using python and the cryptography module?

It's pretty easy using said module to generate the contents of .pem file for a private key:

keyPEMBytes = privateKey.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncryption())

Also easy to generate the contents of a .cer/.pem file for an associated cert:

certBytes = certificate.public_bytes(encoding=serialization.Encoding.PEM)

But I need them (and their chain) balled up on a single .p12 (PKCS12 file). Said module documents how to parse/consume PKCS12 formats, but nothing (that I can find) about how one can generate them.

My understanding of PKI stuff is hit and miss though, so maybe I'm just not searching the right keyword in the documentation?

I can create the .p12 file at the command line on Linux using

openssl pkcs12 -export -out myIdentity.p12 -inkey myPrivKey.pem -in myCert.crt -certfile myCertChain.crt

So I could just wrap calls like this with subprocess/cmd and mess with tempfiles/pipes. I was hoping to keep it all in memory/python though.

Is there a different python TLS library that I should be considering, that can do this?

Upvotes: 14

Views: 15496

Answers (5)

ikreb
ikreb

Reputation: 2785

This use the python module cryptography and generate a p12 file:

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import pkcs12, PrivateFormat

common_name = "John Doe"
password = "secret"

private_key = rsa.generate_private_key(
     public_exponent=65537,
     key_size=4096,
)

cert = x509.load_pem_x509_certificate("cert.pem")

encryption = (
     PrivateFormat.PKCS12.encryption_builder().
     kdf_rounds(50000).
     key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC).
     hmac_hash(hashes.SHA256()).build(f"{password}".encode())
)

p12 = pkcs12.serialize_key_and_certificates(
    f"{common_name}".encode(), private_key, cert, None, encryption
)


with open(f"{common_name}.p12", "wb") as p12_file:
    p12_file.write(p12)

You have to use PBESv1SHA1And3KeyTripleDESCBC because the Windows and macOS certificate store only supports 3DES.

Upvotes: 4

nxmehta
nxmehta

Reputation: 11

Quite annoying that there isn't a clear example documented in the cryptography module. Here's the magic incantation to convert pem files (cert + key) into a p12 file protected by a password.

from cryptography import x509
from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, load_pem_private_key, pkcs12

hostname = "host.domain.com"
crt_file = "cert.pem"
key_file = "privkey.pem"
p12_file = 'host.p12'
password = "password"
with open(crt_file, mode='rb') as file:
  crt = x509.load_pem_x509_certificate(file.read())
with open(key_file, mode='rb') as file:
  key = load_pem_private_key(file.read(), None)
with open(p12_file, 'wb') as file:
  file.write(
      pkcs12.serialize_key_and_certificates(
          hostname.encode(), key, crt, None,
          BestAvailableEncryption(password.encode())))

Upvotes: 1

Paul Kehrer
Paul Kehrer

Reputation: 14089

As you noted, cryptography can parse PKCS12 (at least the subset used by 99.99% of people) with load_key_and_certificates, but serialization to PKCS12 is not currently supported.

I'm one of the core developers for the project and in general cryptography's feature set is driven by users filing issues that explain their use case and need for a particular feature. I'd recommend writing something up on the tracker for discussion. It sounds like your needs would be covered by a simple API looking roughly like:

from cryptography.hazmat.primitives.serialization.pkcs12 import generate_pkcs12

pem_pkcs12 = generate_pkcs12(
    BestAvailableEncryption(b"somepassword"), 
    key, 
    [cert1, cert2]
)

Update: this feature is implemented in pyca/cryptography 3.0:

https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization.html?highlight=pkcs12#cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates

Upvotes: 19

Sean Lancaster
Sean Lancaster

Reputation: 29

#This assumes you have server.key and server.pem already created

import sys
import os
from OpenSSL import crypto

try:
  PFX_Passcode = os.environ['PFX_Passcode']
except KeyError:
  sys.exit('One or more required environment variables not set, required: PFX_Passcode')

with open('server.key', "r") as f:
  privkeydata = f.read()
with open('server.pem', 'r') as f:
  certdata = f.read()
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certdata)
privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkeydata)
p12 = crypto.PKCS12()
p12.set_privatekey(privkey)
p12.set_certificate(cert)
p12data = p12.export(PFX_Passcode)
with open('server.pfx', 'wb') as pfxfile:
  pfxfile.write(p12data)

Upvotes: 2

hd1
hd1

Reputation: 34677

Perhaps the code here might be useful? It's not the cryptography module -- it's chilkat, but it does the job.

import sys
import chilkat

#  First load our certificate into a certificate object,
#  and then get it as a cert chain object.
cert = chilkat.CkCert()

success = cert.LoadFromFile("qa_data/rsaes-oaep/cert.pem")
if (success != True):
    print(cert.lastErrorText())
    sys.exit()

#  This is the certificate PEM that we loaded:

#  -----BEGIN CERTIFICATE-----
#  MIIDozCCAougAwIBAgIJAMRwugDmvniwMA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV
#  BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHV2hlYXRvbjEhMB8GA1UECgwY
#  SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRcwFQYDVQQDDA5DaGlsa2F0V2lkZ2V0
#  czAeFw0xNzA0MTgxMzQxNDVaFw0yMjA0MTcxMzQxNDVaMGgxCzAJBgNVBAYTAlVT
#  MQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHV2hlYXRvbjEhMB8GA1UECgwYSW50ZXJu
#  ZXQgV2lkZ2l0cyBQdHkgTHRkMRcwFQYDVQQDDA5DaGlsa2F0V2lkZ2V0czCCASIw
#  DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXhMR7TNHSaHgrEGvAmHNqw/8xV
#  5TcngVO//yHBniDHas5b2zm0AkhY5EW9dn0wTWHivoRZC2soH7/bxRi4uYUaxgve
#  YLXw6DO2MHSpuTvhSG3+AjsBDa9kXegn9XT1MdPHk9PrHR0sKXGuimkLf4r+Q1oM
#  iGlhbUyRATwVlmo6AndgniDnj2RFBYV8E8SQ+7SnLPJdXZRDbLNVY/DY6D9stRAY
#  rHuz/WBlVW7TQVuWwe7Cd9bfFqjqzLwLksKevWvHGteYDGeiCi+uvMMNfp/Br79Y
#  zJYF/YpTJYE0POBiMwth1FUIYKpZf/O2eUd6RW9h6hocC5QWU9LAq+7kKFMCAwEA
#  AaNQME4wHQYDVR0OBBYEFKBCsni1BfOyH3dSoY8yL4mDeNbpMB8GA1UdIwQYMBaA
#  FKBCsni1BfOyH3dSoY8yL4mDeNbpMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
#  BQADggEBAF2iV69ma5XegVWpxbc0gui63RE8NjW2dBW0fa4a4fwNH2f8oetZEjw4
#  kscx7p4ESxyPtDUkLNplAth+D8NNh9IQDWIld+FZrJsis4tkpVcpMsbmB2CW8fL2
#  IVnVznAN+/PMYEAkeSOnovUGlREHZZu4b9rMVm89+aoCQ9z+byjW9kLKHyvLnUai
#  uEHp+0EVFP91CRSIcTCXsW1c5yZ8k5bqL0HlauuOO4TA3IegyYM9xBubmcxg8cRO
#  2F1k+Ge1lu3e+WY+lihaO0p762dF6g/SUMMrFneCPdIhvt4u7Esc9FDxE3xZEq3x
#  cS5sHTxtTM0LVBi378/x5m1hln5hSJU=
#  -----END CERTIFICATE-----

#  Get it as a certificate chain.
# certChain is a CkCertChain
certChain = cert.GetCertChain()
if (cert.get_LastMethodSuccess() != True):
    print(cert.lastErrorText())
    sys.exit()

#  Next, load the corresponding private key from a PEM.
privKey = chilkat.CkPrivateKey()
success = privKey.LoadPemFile("qa_data/rsaes-oaep/privateKey.pem")
if (success != True):
    print(privKey.lastErrorText())

    sys.exit()

#  This is the private key PEM:

#  -----BEGIN RSA PRIVATE KEY-----
#  MIIEpAIBAAKCAQEAxeExHtM0dJoeCsQa8CYc2rD/zFXlNyeBU7//IcGeIMdqzlvb
#  ObQCSFjkRb12fTBNYeK+hFkLaygfv9vFGLi5hRrGC95gtfDoM7YwdKm5O+FIbf4C
#  OwENr2Rd6Cf1dPUx08eT0+sdHSwpca6KaQt/iv5DWgyIaWFtTJEBPBWWajoCd2Ce
#  IOePZEUFhXwTxJD7tKcs8l1dlENss1Vj8NjoP2y1EBise7P9YGVVbtNBW5bB7sJ3
#  1t8WqOrMvAuSwp69a8ca15gMZ6IKL668ww1+n8Gvv1jMlgX9ilMlgTQ84GIzC2HU
#  VQhgqll/87Z5R3pFb2HqGhwLlBZT0sCr7uQoUwIDAQABAoIBAGCP5LWDIWzpLFHa
#  or6gCqKZjyo6nFFO4Ohqn+jsH+slBTTQVGmTMy302uhBbYnnwUtMJ+ZTwaO3/85T
#  Q5otwrJ2f0CZcx42AkoB1SGJFVBoPj0WoCYE/JWjZ8P7g+dgI8GR+cyHRjzPKSZF
#  o1thdgrwyxMtXH/4QCsF89FLQ4xwCJUkj+w0KBkX8isWcBJnKip81MK8rxhgMJ6v
#  5+DBVvlvm4BRz0hva7HB3TaZgo9ZWZdoSJQ/l33yfIL8H0EJjC6uJ/Bn77JtDzcv
#  1oUF02Ebu8/Re7RpUqdTIb1GamrOCjuZN66SrCOsh9rRzueU3UTLzjb9FDv8FQDA
#  5Dm59KECgYEA6zJ2LaiUywth80rAC34jEF85aC3vyKK0MxYELLB9Nw6MNFD1PZly
#  YkOcI2kxVV+1KqtRYFlfEpCL4flqVnqjVj5jTCCOXqO6RjnPLoupf0yLpj60raO6
#  Ouw5EDGjfUDBQtkCQNOx0nZLftVy30Ck/V2gVw9sEgWQV0YRavuymKMCgYEA12HB
#  eHHSo/dK7keiIK7RRQu2ibALXWXsGq9lUbnQAunKRCa3qQtc8OwGRZsdj9u8HwCB
#  zDu5QIbaQCa1SyDz1CsOysOfTYNhh1SZ4GLiu7/b0v5ktgdtosT9niowo08Y4ZU+
#  lA8p2eriylznacNOkA8O1g8m1etHqV+cVKUMvJECgYEApoeVO6DosZd+pCfiMIEA
#  TMBPwl78L6BcXOuSyx2vmwMW7JgB/LqgTck3Ta9Wue9JNEwXE1RUR63HDZpu24p8
#  Cj2kSyumAoaFBuI5uugyfCrzmiM+BNAvtTa69Kdg7feinzVOLh52AuG1dLmnW0HH
#  UIoxVFNcHqqH1/OJs6lBfGcCgYEAkQXhn5gEnSMF8SuKnzcW8cbRQiSnglv+fnlb
#  X4wd49hHuVvYjpp4GkDSeEvZu+PY54cP3tTYB/rFOizWJaAqjdHwQ9c0jJzKLrVo
#  Zw4uXPzypz9j0K1vxQmfO8Bsv4mi10IGwOr04yalTlz/9NbtL1L0Sm4pWCD0P7eB
#  K/12dgECgYBXNV5NcLpZhpjo8q05xAiFuwC2e29qYyVwcFVhYdYguutRKImZtjSE
#  dndNAxa7EDL9NRc5SjX4NHAZoImADRSbgc2rIq2ePY8WORaR+iKsVx7PJGHSiXTL
#  30X9E3com8ctyTsZTnlJ4Dm7SefkN0NKzqwjxXxZLaUteWBPUt+YAg==
#  -----END RSA PRIVATE KEY-----

#  Create a PFX object instance, and add the private key + cert chain.
pfx = chilkat.CkPfx()
success = pfx.AddPrivateKey(privKey,certChain)
if (success != True):
    print(pfx.lastErrorText())

    sys.exit()

#  Finally, write the PFX w/ a password.
success = pfx.ToFile("pfxPassword","qa_output/sample.pfx")
if (success != True):
    print(pfx.lastErrorText())
    sys.exit()

print("Success.")

Upvotes: 2

Related Questions