Last active
July 17, 2024 02:03
-
-
Save markscottwright/86f29f8d4e9e02d2533efced6cb24083 to your computer and use it in GitHub Desktop.
expand a pkcs12 file into certificate and key files in PEM format, in python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# require cryptography and asn1crypto | |
import base64 | |
import os | |
from asn1crypto.keys import PrivateKeyInfo | |
from asn1crypto.pkcs12 import Pfx, SafeContents | |
from cryptography.hazmat.primitives.hashes import SHA256 | |
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | |
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes | |
def decrypt_certificates(encrypted_content_info, password: bytes): | |
kdf = encrypted_content_info["content_encryption_algorithm"]['parameters']['key_derivation_func'] | |
key_length = kdf['parameters']['key_length'].native | |
salt = kdf['parameters']['salt'].native | |
iteration_count = kdf['parameters']['iteration_count'].native | |
ciphertext = encrypted_content_info['encrypted_content'].native | |
iv = encrypted_content_info["content_encryption_algorithm"]['parameters']['encryption_scheme']['parameters'].native | |
plaintext = decrypt_pbes2(password, salt, iteration_count, key_length, iv, ciphertext) | |
for safebag in SafeContents.load(plaintext): | |
cert = safebag["bag_value"]["cert_value"].parsed | |
cert_der = cert.dump() | |
friendly_name = local_key_id = 'unknown' | |
for attr in safebag["bag_attributes"].native: | |
if attr['type'] == 'friendly_name': | |
friendly_name = attr["values"][0] | |
if attr['type'] == 'local_key_id': | |
local_key_id = attr["values"][0] | |
yield friendly_name, local_key_id, cert_der | |
def decrypt_pbes2(password: bytes, salt: bytes, iteration_count: int, key_length: int, iv: bytes, ciphertext: bytes): | |
key = PBKDF2HMAC(algorithm=SHA256(), salt=salt, iterations=iteration_count, length=key_length).derive( | |
password) | |
decryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor() | |
plaintext = decryptor.update(ciphertext) + decryptor.finalize() | |
return plaintext | |
def main(pkcs12_file, password, outdir): | |
if isinstance(password, str): | |
password = password.encode("utf-8") | |
with open(pkcs12_file, "rb") as input_file: | |
p12bytes = input_file.read() | |
# https://datatracker.ietf.org/doc/html/rfc7292 | |
p12 = Pfx.load(p12bytes) | |
for content_info in p12.authenticated_safe: | |
if content_info["content_type"].native == 'encrypted_data': | |
encrypted_content_info = content_info["content"]["encrypted_content_info"] | |
for friendly_name, local_key_id, cert_der in decrypt_certificates(encrypted_content_info, password): | |
filename = outdir + "/" + friendly_name + ".pem" | |
print(f"writing certificate {filename}") | |
with open(filename, "wb") as f: | |
f.write(b"-----BEGIN CERTIFICATE-----\n" + base64.encodebytes( | |
cert_der) + b"-----END CERTIFICATE-----\n") | |
elif content_info["content_type"].native == 'data': | |
sc = SafeContents.load(content_info["content"].native) | |
for safebag in sc: | |
friendly_name = "unknown" | |
for attr in safebag["bag_attributes"].native: | |
if attr['type'] == 'friendly_name': | |
friendly_name = attr["values"][0] | |
# if attr['type'] == 'local_key_id': | |
# local_key_id = attr["values"][0] | |
if safebag["bag_id"].native == "cert_bag": | |
cert_der = safebag["bag_value"]["cert_value"].parsed.dump() | |
filename = outdir + "/" + friendly_name + ".pem" | |
print(f"writing certificate {filename}") | |
with open(filename, "wb") as f: | |
f.write(b"-----BEGIN CERTIFICATE-----\n" + base64.encodebytes( | |
cert_der) + b"-----END CERTIFICATE-----\n") | |
elif safebag["bag_id"].native == "pkcs8_shrouded_key_bag": | |
enc_alg = safebag["bag_value"]["encryption_algorithm"] | |
assert enc_alg["algorithm"].native == 'pbes2' | |
assert enc_alg["parameters"]["key_derivation_func"]["algorithm"].native == 'pbkdf2' | |
assert enc_alg["parameters"]["encryption_scheme"]["algorithm"].native == "aes256_cbc" | |
safe_contents_bytes = decrypt_pbes2( | |
password, | |
enc_alg["parameters"]["key_derivation_func"]["parameters"]["salt"].native, | |
enc_alg["parameters"]["key_derivation_func"]["parameters"]["iteration_count"].native, | |
enc_alg["parameters"]["key_derivation_func"]["parameters"]["key_length"].native, | |
enc_alg["parameters"]["encryption_scheme"]["parameters"].native, | |
safebag["bag_value"]["encrypted_data"].native) | |
private_key = PrivateKeyInfo.load(safe_contents_bytes) | |
filename = outdir + "/" + friendly_name + ".key" | |
print(f"writing key {filename}") | |
with open(filename, "wb") as f: | |
f.write(b"-----BEGIN PRIVATE KEY-----\n" + base64.encodebytes( | |
private_key.dump()) + b"-----END PRIVATE KEY-----\n") | |
os.makedirs("./out", exist_ok=True) | |
main("test.p12", "foo123", "./out") | |
# main("c:/Program Files/Eclipse Adoptium/jdk-21.0.1.12-hotspot/lib/security/cacerts", "changeit", "./out") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment