Skip to content

Instantly share code, notes, and snippets.

@sbealer
Last active March 5, 2026 20:21
Show Gist options
  • Select an option

  • Save sbealer/0744fc6680a71f6fa0a2414b645fa66d to your computer and use it in GitHub Desktop.

Select an option

Save sbealer/0744fc6680a71f6fa0a2414b645fa66d to your computer and use it in GitHub Desktop.
# RUN THIS LIKE:
# python gen_pem_cert.py --for_process "sharepoint_etl" --password "MyStrongPass123!" --out-dir ./certs
"""
Generate a password-protected private key (PEM) + a self-signed certificate (PEM).
Outputs:
- private_key_encrypted.pem
- certificate.pem
Install dependency:
pip install cryptography
"""
from __future__ import annotations
import argparse
import datetime as dt
from pathlib import Path
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
CERT_VALID_YEARS = 100 # effectively "never expires"
def generate_key_rsa(key_size: int = 2048) -> rsa.RSAPrivateKey:
return rsa.generate_private_key(public_exponent=65537, key_size=key_size)
def build_self_signed_cert(
private_key: rsa.RSAPrivateKey,
for_process: str,
) -> x509.Certificate:
name = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "CA"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Chico"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Example Org"),
x509.NameAttribute(NameOID.COMMON_NAME, for_process),
]
)
now = dt.datetime.utcnow()
not_before = now - dt.timedelta(minutes=5)
# "Never expires" isn't allowed in X.509, so we set it far in the future
not_after = now + dt.timedelta(days=365 * CERT_VALID_YEARS)
builder = (
x509.CertificateBuilder()
.subject_name(name)
.issuer_name(name)
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(not_before)
.not_valid_after(not_after)
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(
x509.KeyUsage(
digital_signature=True,
key_encipherment=True,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
),
critical=True,
)
.add_extension(
x509.ExtendedKeyUsage(
[ExtendedKeyUsageOID.CLIENT_AUTH]
),
critical=False,
)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(for_process)]),
critical=False,
)
)
return builder.sign(private_key=private_key, algorithm=hashes.SHA256())
def write_encrypted_private_key_pem(
private_key: rsa.RSAPrivateKey,
password: str,
path: Path,
) -> None:
pem_bytes = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(password.encode("utf-8")),
)
path.write_bytes(pem_bytes)
def write_certificate_pem(cert: x509.Certificate, path: Path) -> None:
path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
def main() -> int:
parser = argparse.ArgumentParser(
description="Generate encrypted PEM private key + self-signed cert."
)
parser.add_argument(
"--for_process",
required=True,
help="Identifier for what this cert/key is for (used as CN + SAN).",
)
parser.add_argument("--key-size", type=int, default=2048, help="RSA key size (2048/3072/4096).")
parser.add_argument("--password", required=True, help="Password to encrypt the private key PEM.")
parser.add_argument("--out-dir", default=".", help="Output directory.")
parser.add_argument("--key-name", default="private_key_encrypted.pem", help="Encrypted private key PEM filename.")
parser.add_argument("--cert-name", default="certificate.pem", help="Certificate PEM filename.")
args = parser.parse_args()
out_dir = Path(args.out_dir).expanduser().resolve()
out_dir.mkdir(parents=True, exist_ok=True)
key_path = out_dir / args.key_name
cert_path = out_dir / args.cert_name
private_key = generate_key_rsa(key_size=args.key_size)
cert = build_self_signed_cert(private_key, for_process=args.for_process)
write_encrypted_private_key_pem(private_key, args.password, key_path)
write_certificate_pem(cert, cert_path)
print("Wrote:")
print(f" Encrypted private key: {key_path}")
print(f" Certificate (public): {cert_path}")
print(f"\nCertificate validity: {CERT_VALID_YEARS} years")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment