CryptoSys Home > PKI > How to process a PFX file

How to process a PFX file


I paid for a signing certificate and they sent me a PFX (.p12) file. How do I extract my certificate and key?

This page explains how you can use CryptoSys PKI Pro to extract your X.509 certificate and private key, extract all the other certificates in the "chain" up to the root certificate, and compose a XADES <xades:Cert> element.

C# code | XAdES | Non-ASCII characters | Create the test certificates | Python | Downloads | Contact us

Firstly, note that a .PFX file and a .P12 file are exactly the same thing. They are both used as extensions for PKCS#12 files described in RFC 7292, PKCS #12: Personal Information Exchange Syntax v1.1 .

You can probably put just about anything in a PFX file, but the conventional contents are typically

Some C# code to process the PFX file

Here is some code in C# to process a PFX file and extract some useful files. The test file is enid.pfx (zipped, 2.7 kB). This has been created for "Enid" by an intermediate CA "Ian" with a root CA "Carl". The password is, as usual, "password".

  1. Show we can read in private key just like we can from a PKCS#8 key file.
  2. Extract our X.509 certificate.
  3. Extract all certificates in the chain.
  4. Show details of all these certificates.
  5. Compose a <xades:Cert> element for a XADES file.

You can see how to validate the certificates in this chain at How to Validate a Certificate Chain, and how we created the keys and certificates in How did you create the test certificates? below.

static void ProcessPFX()
{
	string pfxfile, pwd;
	string certfile, p7file;
	StringBuilder sbPriKey, sbPubKey;
	int r, nCerts, i;
	string fname, s, query;
	string template, xmlelement, hashalg;


	// Our PFX file (could also be .p12 = same thing)
	pfxfile = "enid.pfx";
	pwd = "password";   // Danger, Will Robinson!

	// 1. First show we can read in our private key directly from the PFX file 
	// (just like from a PKCS#8 key file)
	// We can use this "internal" private key string to, e.g., sign data.
	sbPriKey = Rsa.ReadPrivateKey(pfxfile, pwd);
	Console.WriteLine("Rsa.ReadPrivateKey() returns an internal string of length {0}", sbPriKey.Length);
	Debug.Assert(sbPriKey.Length > 0, "Failed to read private key from PFX");
	Console.WriteLine("Pri key bits = {0}", Rsa.KeyBits(sbPriKey.ToString()));

	// 2. Extract our X.509 certificate
	certfile = "mycert.cer";
	r = X509.GetCertFromPFX(certfile, pfxfile, pwd);
	Console.WriteLine("X509.GetCertFromPFX() returns {0} (expected +ve)", r);

	// 2a. Read in our public key from this certificate
	sbPubKey = Rsa.ReadPublicKey(certfile);
	Debug.Assert(sbPubKey.Length > 0, "Failed to read public key from certificate");
	Console.WriteLine("Pub key bits = {0}", Rsa.KeyBits(sbPubKey.ToString()));

	// 2b. Show these two keys are matched
	Console.WriteLine("Pri key hashcode = {0:X8}", Rsa.KeyHashCode(sbPriKey));
	Console.WriteLine("Pub key hashcode = {0:X8}", Rsa.KeyHashCode(sbPubKey));

	// 2c. alternatively...
	r = Rsa.KeyMatch(sbPriKey.ToString(), sbPubKey.ToString());
	Console.WriteLine("Rsa.KeyMatch() returns {0} (expected 0)", r);
	Debug.Assert(0 == r, "Keys do not match");

	// 3. Extract all certificates in the chain as a single P7 file
	// (strictly should be .p7c but .p7b works better in Windows)
	p7file = "certs.p7b";
	r = X509.GetP7ChainFromPFX(p7file, pfxfile, pwd);
	Console.WriteLine("X509.GetP7ChainFromPFX() returns {0} (expected +ve)", r);
	Debug.Assert(r > 0, "Failed to extract P7 file from PFX");

	// 3a. Validate the chain
	r = X509.ValidatePath(p7file);
	Console.WriteLine("X509.ValidatePath() returns {0} (expected 0)", r);
	Debug.Assert(r == 0, "Path validation failed");

	// 4. Extract certs individually from the P7 file
	//    and show details of these certificates.

	// How many certs are there?
	nCerts = (int)X509.GetCertFromP7Chain("", p7file, 0);
	Console.WriteLine("nCerts = {0}", nCerts);
	Debug.Assert(nCerts > 0, "No certs found in P7 file");

	// Enumerate through the certs
	for (i = 1; i <= nCerts; i++) {
		fname = "cert" + i + ".cer";
		Console.WriteLine("FILE: {0}", fname);
		r = X509.GetCertFromP7Chain(fname, p7file, i);
		Debug.Assert(r > 0);

		// 4a. Extract details from certificate in XML-DSIG-required form (LDAP, decimal)
		query = "subjectName";
		s = X509.QueryCert(fname, query, X509.OutputOpts.Ldap);
		Console.WriteLine("{0}: {1}", query, s);
		query = "issuerName";
		s = X509.QueryCert(fname, query, X509.OutputOpts.Ldap);
		Console.WriteLine("{0}: {1}", query, s);
		query = "serialNumber";
		s = X509.QueryCert(fname, query, X509.OutputOpts.Decimal);
		Console.WriteLine("{0}: {1}", query, s);

		// And compute its SHA-1 digest value ("thumbprint")
		s = X509.CertThumb(fname, HashAlgorithm.Sha1);
		Console.WriteLine("DigestValue (hex) = {0}", s);
		// We want the base64-encoded form for XML DigestValue elements
		Console.WriteLine("DigestValue (b64) = {0}", Cnv.ToBase64(Cnv.FromHex(s)));
	}

	// 5. Compose a <xades:Cert> element for a XADES file
	Console.WriteLine("FILE: {0}", certfile);
	template = @"<xades:Cert>
  <xades:CertDigest>
    <ds:DigestMethod Algorithm=""@!HASH-ALG!@"" />
    <ds:DigestValue>@!DIGEST-VALUE!@</ds:DigestValue>
  </xades:CertDigest>
    <xades:IssuerSerial>
      <ds:X509IssuerName>@!ISSUER-NAME!@</ds:X509IssuerName>
      <ds:X509SerialNumber>@!SERIAL-NUMBER!@</ds:X509SerialNumber>
  </xades:IssuerSerial>
</xades:Cert>";
	// Get element content in required form (LDAP, decimal) from cert then substitute in template.
	xmlelement = template;
	// 5a. Use SHA-1
	hashalg = "http://www.w3.org/2000/09/xmldsig#sha1";
	xmlelement = xmlelement.Replace("@!HASH-ALG!@", hashalg);
	s = X509.CertThumb(certfile, HashAlgorithm.Sha1);
	s = Cnv.ToBase64(Cnv.FromHex(s));
	xmlelement = xmlelement.Replace("@!DIGEST-VALUE!@", s);
	s = X509.QueryCert(certfile, "issuerName", X509.OutputOpts.Ldap);
	xmlelement = xmlelement.Replace("@!ISSUER-NAME!@", s);
	s = X509.QueryCert(certfile, "serialNumber", X509.OutputOpts.Decimal);
	xmlelement = xmlelement.Replace("@!SERIAL-NUMBER!@", s);
	Console.WriteLine(xmlelement);

	// 5b. Repeat using SHA-256
	xmlelement = template;
	hashalg = "http://www.w3.org/2001/04/xmlenc#sha256";    // CHANGED
	xmlelement = xmlelement.Replace("@!HASH-ALG!@", hashalg);
	s = X509.CertThumb(certfile, HashAlgorithm.Sha256);     // CHANGED
	s = Cnv.ToBase64(Cnv.FromHex(s));
	xmlelement = xmlelement.Replace("@!DIGEST-VALUE!@", s);
	s = X509.QueryCert(certfile, "issuerName", X509.OutputOpts.Ldap);
	xmlelement = xmlelement.Replace("@!ISSUER-NAME!@", s);
	s = X509.QueryCert(certfile, "serialNumber", X509.OutputOpts.Decimal);
	xmlelement = xmlelement.Replace("@!SERIAL-NUMBER!@", s);
	Console.WriteLine(xmlelement);
}

Output:

Rsa.ReadPrivateKey() returns an internal string of length 848
Pri key bits = 1024
X509.GetCertFromPFX() returns 517 (expected +ve)
Pub key bits = 1024
Pri key hashcode = 05C4F764
Pub key hashcode = 05C4F764
Rsa.KeyMatch() returns 0 (expected 0)
X509.GetP7ChainFromPFX() returns 1547 (expected +ve)
X509.ValidatePath() returns 0 (expected 0)
nCerts = 3
FILE: cert1.cer
subjectName: CN=Enid
issuerName: CN=Ian,O=Administraci\C3\B3n de certificados
serialNumber: 206793668471960662
DigestValue (hex) = 2a14c7faf59de44f928dc2fdce300f818619c4b9
DigestValue (b64) = KhTH+vWd5E+SjcL9zjAPgYYZxLk=
FILE: cert2.cer
subjectName: CN=Ian,O=Administraci\C3\B3n de certificados
issuerName: CN=CarlRSA
serialNumber: 12290
DigestValue (hex) = 004ff0e76f67580b663019347079d5f61e80c9b9
DigestValue (b64) = AE/w529nWAtmMBk0cHnV9h6Aybk=
FILE: cert3.cer
subjectName: CN=CarlRSA
issuerName: CN=CarlRSA
serialNumber: 93318145165434344057210696408795074592
DigestValue (hex) = 4110908f77c64c0edfc2de6273bfa9a98a9c5ce5
DigestValue (b64) = QRCQj3fGTA7fwt5ic7+pqYqcXOU=

Compose a XAdES Cert element

Here is the output that you could use in a XADES <xades:Cert> element. Two alternatives: one using SHA-1, the other using SHA-256.

<xades:Cert>
  <xades:CertDigest>
    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <ds:DigestValue>KhTH+vWd5E+SjcL9zjAPgYYZxLk=</ds:DigestValue>
  </xades:CertDigest>
    <xades:IssuerSerial>
      <ds:X509IssuerName>CN=Ian,O=Administraci\C3\B3n de certificados</ds:X509IssuerName>
      <ds:X509SerialNumber>206793668471960662</ds:X509SerialNumber>
  </xades:IssuerSerial>
</xades:Cert>

<xades:Cert>
  <xades:CertDigest>
    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
    <ds:DigestValue>2DWLLLzWKY0b6HrHxL67ljdND2kdGtQljCJrRxtp1sE=</ds:DigestValue>
  </xades:CertDigest>
    <xades:IssuerSerial>
      <ds:X509IssuerName>CN=Ian,O=Administraci\C3\B3n de certificados</ds:X509IssuerName>
      <ds:X509SerialNumber>206793668471960662</ds:X509SerialNumber>
  </xades:IssuerSerial>
</xades:Cert>

Dealing with non-ASCII characters

Note that Ian's intermediate certificate has the accented character ó (U+00F3, latin small letter o with accent) in the organization name "O=Administración de certificados". We use the X509.OutputOpts.Ldap option when extracting the issuerName. This gives us the escaped UTF-8-encoded character "\C3\B3" in the output.

This is according to the Distinguished Name Encoding Rules of [XML-DSIG] for an <ds:X509IssuerName> element.

How did you create the test certificates?

Here is the code used to create the test certificates MakeCertChain.cs. The root CA Carl's private key and root certificate is here CarlPrivRSASign.zip.

Same again using Python

Here is some code in Python that does more or less the same as the C# code above.

# Extract P7 certificate chain from PFX and process certificates.
# @module processpfx.py
# @license MIT

from cryptosyspki import *
pfxname = 'enid.pfx'
pwd = 'password'   #!!DANGER!!
# We can read in the private key directly from the PFX file
prikey = Rsa.read_private_key(pfxname, pwd)
print "Pri key bits =", Rsa.key_bits(prikey)

# We can extract the user's certificate file
mycert = 'Certificado.cer'
X509.get_cert_from_pfx(mycert, pfxname, pwd)
print "Extracted certificate file '" + mycert + "'"
# and read the public key from that file
pubkey = Rsa.read_public_key(mycert)
print "Pub key bits =", Rsa.key_bits(pubkey)

# And show the two keys are matched
print "Pri key hashcode=0x" + Rsa.key_hashcode(prikey)
print "Pub key hashcode=0x" + Rsa.key_hashcode(pubkey)
# alternatively
ismatch = Rsa.key_match(prikey, pubkey)
print "Key match is", ismatch
assert(ismatch)

# We can extract all the certs in one P7 chain file
# (Use .p7b on Windows)
p7name = 'certs.p7b'
X509.get_p7chain_from_pfx(p7name, pfxname, pwd)
# We can extract all the individual certs and get their details
n = X509.get_cert_count_from_p7(p7name)
print "ncerts =", n
for i in xrange(n):
    certname = "cert" + str(i+1) + ".cer"
    print "FILE: " + certname
    X509.get_cert_from_p7(certname, p7name, i + 1)
    subjname = X509.query_cert(certname, "subjectName", X509.Opts.LDAP)
    issuname = X509.query_cert(certname, "issuerName", X509.Opts.LDAP)
    serialnum = X509.query_cert(certname, "serialNumber", X509.Opts.DECIMAL)
    print "subjectName : ", subjname
    print "issuerName  : ", issuname
    print "serialNumber: ", serialnum
    digval = X509.cert_thumb(certname, hashalg=X509.HashAlg.SHA1)
    # We want base64 digest not hex
    print "digestValue (hex) = ", digval
    print "digestValue (b64) = ", Cnv.tobase64(Cnv.fromhex(digval))

Expected output:

Pri key bits = 1024
Extracted certificate file 'Certificado.cer'
Pub key bits = 1024
Pri key hashcode=0x05C4F764
Pub key hashcode=0x05C4F764
Key match is True
ncerts = 3
FILE: cert1.cer
subjectName :  CN=Enid
issuerName  :  CN=Ian,O=Administraci\C3\B3n de certificados
serialNumber:  206793668471960662
digestValue (hex) =  2a14c7faf59de44f928dc2fdce300f818619c4b9
digestValue (b64) =  KhTH+vWd5E+SjcL9zjAPgYYZxLk=
FILE: cert2.cer
subjectName :  CN=Ian,O=Administraci\C3\B3n de certificados
issuerName  :  CN=CarlRSA
serialNumber:  12290
digestValue (hex) =  004ff0e76f67580b663019347079d5f61e80c9b9
digestValue (b64) =  AE/w529nWAtmMBk0cHnV9h6Aybk=
FILE: cert3.cer
subjectName :  CN=CarlRSA
issuerName  :  CN=CarlRSA
serialNumber:  93318145165434344057210696408795074592
digestValue (hex) =  4110908f77c64c0edfc2de6273bfa9a98a9c5ce5
digestValue (b64) =  QRCQj3fGTA7fwt5ic7+pqYqcXOU=

Download and source code

Source C# code ExtractFromPFX.cs and extracted certificate files (zipped 3.2 kB).

All the above files in one zip file (14 kB).

Contact us

To contact us or comment on this page, please send us a message.

[Go to top]

This page first published 21 January 2019. Last updated 10 June 2019.