How to Get Private Key from Certificate in an Azure Key Vault?

Question:

I have a Certificate in an Azure Key Vault that I would like to extract a private key from.

According to the Microsoft Docs:

When a Key Vault certificate is created, an addressable key and secret are also created with the same name. The Key Vault key allows key operations and the Key Vault secret allows retrieval of the certificate value as a secret.

However, I have been unsuccessful in extracting the private key from this. Here is an example of some python code I tried:

pem_data  = get_secret('https://keyvault.azure.net/', 'x509-cert')
pem_data = '-----BEGIN CERTIFICATE----- ' + pem_data + ' -----END CERTIFICATE-----'
pem_data = pem_data.encode()
key = x509.load_pem_x509_certificate(pem_data,  backend=default_backend())
private_key = key.private_key()

This however, will error saying it cannot load the certificate.

Asked By: AlanPear

||

Answers:

The pem_data you get from the key vault is already in pem format, and you can ony get the public key.

pem_data = client.get_secret("https://XX.vault.azure.net/", "XX", "XX")
pem_data = pem_data.value.encode()

cert = load_pem_x509_certificate(pem_data,  backend=default_backend())
public_key = cert.public_key()

If you want to get the private key, you can use OpenSSL:

import OpenSSL.crypto

pem_data = client.get_secret("https://XX.vault.azure.net/", "XX", "XX")
pem_data = pem_data.value.encode()
crtObj = crypto.load_certificate(crypto.FILETYPE_PEM, pem_data)
pubKeyObject = crtObj.get_pubkey()
priKeyString = crypto.dump_privatekey(crypto.FILETYPE_PEM, pubKeyObject)
print(priKeyString)

Note:

Please make sure that you have indicated that the key is exportable when you create the certificate. If the policy indicates non-exportable, then the private key isn’t a part of the value when retrieved as a secret. Refer to this document for more details.

enter image description here

Answered By: Tony Ju

There’s now a sample for azure-keyvault-certificates that shows how to get the private key from a certificate using pyOpenSSL:

import base64
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from cryptography.hazmat.primitives.serialization import pkcs12

vault_url = "https://{vault-name}.vault.azure.net"
cert_name = "certificate name"
credential = DefaultAzureCredential()

secret_client = SecretClient(vault_url=vault_url, credential=credential)
certificate_secret = secret_client.get_secret(name=cert_name)

# Now we can extract the private key and public certificate from the secret using the cryptography
# package.
# This example shows how to parse a certificate in PKCS12 format since it's the default in Key Vault,
# but PEM certificates are supported as well. With a PEM certificate, you could use load_pem_private_key
# in place of load_key_and_certificates.
cert_bytes = base64.b64decode(certificate_secret.value)
private_key, public_certificate, additional_certificates = pkcs12.load_key_and_certificates(
    data=cert_bytes,
    password=None
)

More documentation on the new Azure SDK packages for Key Vault (that replace azure-keyvault) can be found here:

(I work on the Azure SDK in Python)

Answered By: mccoyp

For anyone facing this issue, this gist will help you a lot https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068?permalink_comment_id=4503917#gistcomment-4503917

secrets_client = SecretClient(vault_url=vault_url, credential=credential)
certificate = secrets_client.get_secret(certificate_name)

Once you do that, then you will have the .pfx file stored in the certificate.value variable (encoded in base64). If you want to use that with this solution, then the code would be the following:

from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from base64 import b64decode

import requests
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates


@contextmanager
def pfx_to_pem(pfx_content, pfx_password):
    ''' Decrypts the .pfx file to be used with requests. '''
    pfx = b64decode(pfx_content)
    private_key, main_cert, add_certs = load_key_and_certificates(pfx, pfx_password.encode('utf-8'), None)

    with NamedTemporaryFile(suffix='.pem') as t_pem:
        with open(t_pem.name, 'wb') as pem_file:
            pem_file.write(private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()))
            pem_file.write(main_cert.public_bytes(Encoding.PEM))
            for ca in add_certs:
                pem_file.write(ca.public_bytes(Encoding.PEM))
        yield t_pem.name

# HOW TO USE:
# secrets_client = SecretClient(vault_url=vault_url, credential=credential)
# certificate = secrets_client.get_secret(certificate_name)
# with pfx_to_pem(certificate.value, 'bar') as cert:
#     requests.post(url, cert=cert, data=payload)
Answered By: alvaro avila