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
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".
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=
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>
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.
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.
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=
Source C# code ExtractFromPFX.cs and extracted certificate files (zipped 3.2 kB).
All the above files in one zip file (14 kB).
To contact us or comment on this page, please send us a message.
This page first published 21 January 2019. Last updated 10 June 2019.