Back to Blog
Python

Python Cryptography PEM Key Serialization

python cryptography pem key serialization: Learn how to load, serialize, and save PEM keys with the Python cryptography library, including password encryption and form...

cryptographyPEMkey serializationPythonOpenSSL
Illustration of a Python code block converting a key object into a PEM file with a lock icon representing encryption.

When working with cryptographic keys in Python, the cryptography library provides a consistent API for loading and serializing PEM keys. This article covers python cryptography pem key serialization: reading keys from disk, converting them to PEM format, and saving them with the appropriate encoding and encryption.

The library's serialization module is the central point for these operations. It defines the encoding, format, and encryption algorithms that determine how a key is represented on disk. Understanding these options is important because PEM files are not a single format—they can contain different key types and headers depending on how they were generated.

Loading a PEM Key with cryptography

Before serializing a key, you usually need to load it from a file or a string. The cryptography library provides two functions for this: load_pem_private_key() and load_pem_public_key(). Both accept bytes and return the corresponding key object.

from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key # Load a private key from a file with open("private_key.pem", "rb") as f: private_key = load_pem_private_key(f.read(), password=None) # Load a public key from a file with open("public_key.pem", "rb") as f: public_key = load_pem_public_key(f.read())

The password parameter is required for private keys. If the key is encrypted, you must provide the password as bytes; otherwise, pass None. The library will raise ValueError if the password is incorrect or if the key format is unsupported.

For keys that are not PEM but DER, you can use load_der_private_key() and load_der_public_key(). The loading functions automatically detect the key type (RSA, EC, Ed25519, etc.) from the PEM structure.

Serializing a Private Key to PEM

Once you have a private key object, you can serialize it to PEM bytes using the private_bytes() method. This method requires three arguments: encoding, format, and encryption algorithm.

from cryptography.hazmat.primitives import serialization pem_data = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) with open("private_key_export.pem", "wb") as f: f.write(pem_data)

The most common encoding is PEM, which produces ASCII-armored output with -----BEGIN ...----- headers. The format determines the internal structure. PKCS8 is the modern standard and is widely compatible with OpenSSL and other tools. TraditionalOpenSSL (also called PKCS#1 for RSA) is older but still encountered. The encryption algorithm can be NoEncryption() or BestAvailableEncryption(password).

If you need the key as a string instead of bytes, decode it with UTF-8:

pem_str = pem_data.decode("utf-8")

Serializing a Public Key to PEM

Public keys are serialized similarly using public_bytes(). The format is typically PublicFormat.SubjectPublicKeyInfo, which is the standard for public keys in PEM.

public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) with open("public_key_export.pem", "wb") as f: f.write(public_pem)

Unlike private keys, public keys are never encrypted. The SubjectPublicKeyInfo format wraps the key in an X.509 structure, which is what most tools expect. The older PKCS1 format is specific to RSA and is rarely used for public keys in modern systems.

Encrypting a Private Key with a Password

When storing a private key, you often want to protect it with a password. The cryptography library provides BestAvailableEncryption(), which uses a strong algorithm (AES-256-CBC with PBKDF2 by default). This is the recommended approach for new keys.

password = b"correct horse battery staple" encrypted_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(password) ) with open("encrypted_private_key.pem", "wb") as f: f.write(encrypted_pem)

The resulting PEM file will contain an ENCRYPTED marker in the header. To load it back, pass the same password to load_pem_private_key().

with open("encrypted_private_key.pem", "rb") as f: key = load_pem_private_key(f.read(), password=password)

If you attempt to load an encrypted key without a password, the library raises TypeError. If the password is wrong, it raises ValueError. Always handle these exceptions in production code to avoid crashing.

Choosing Between PKCS#1 and PKCS#8

The PrivateFormat enum offers two main options: TraditionalOpenSSL and PKCS8. The choice affects compatibility and security.

FormatStructureCompatibilityUse case
TraditionalOpenSSLPKCS#1 (RSA) or SEC1 (EC)Older tools, some legacy systemsWhen you must match an existing legacy format
PKCS8Standardized, supports all key typesOpenSSL, Java, .NET, most modern librariesRecommended for new implementations

PKCS8 is more flexible because it can encapsulate any key type (RSA, EC, Ed25519) and supports stronger encryption algorithms. TraditionalOpenSSL is limited to specific key types and uses older encryption schemes. For new code, prefer PKCS8.

Common Errors and Compatibility Issues

Serialization often fails because of mismatched expectations. Here are a few pitfalls you may encounter.

Unsupported Key Type

If you try to serialize a key type that does not match the format, the library raises ValueError. For example, using TraditionalOpenSSL with an Ed25519 key will fail because that format does not support Ed25519. Always use PKCS8 for non-RSA keys.

Password Encoding

The password must be bytes, not str. A common mistake is passing a string, which raises TypeError. Convert with password.encode("utf-8") if you have a string.

File Permissions

When writing private keys, set restrictive file permissions to prevent unauthorized access. On Unix, use os.chmod() or write with open() and then change permissions. The cryptography library does not manage file permissions automatically.

import os with open("private.pem", "wb") as f: f.write(pem_data) os.chmod("private.pem", 0o600)

Inconsistent Headers

Some tools expect specific PEM headers. The cryptography library generates standard headers, but if you are interchanging with a system that expects a particular label (e.g., RSA PRIVATE KEY vs PRIVATE KEY), you may need to convert formats. PKCS8 produces BEGIN PRIVATE KEY, while TraditionalOpenSSL for RSA produces BEGIN RSA PRIVATE KEY.

Handling Key Formats in Production

In production, key serialization often happens during key rotation, certificate generation, or when exporting keys to other services. The main concern is consistency: ensure that the format you write matches what the consumer expects.

For example, if you are generating a key for an internal service that uses OpenSSL, PKCS8 with NoEncryption is usually safe. If you are storing keys in a secrets manager, you might prefer to keep them encrypted at rest and only decrypt them in memory.

Another consideration is the performance of loading and serializing keys. These operations are not typically a bottleneck because they happen once per process or per key generation. The overhead is dominated by cryptographic operations like key generation and signing, not by the serialization step itself.

Finally, be aware that the cryptography library's default encryption algorithm may change over time. If you need to produce keys that are readable by very old OpenSSL versions, you may need to specify a legacy encryption algorithm explicitly. The library does not expose a direct option for that, so you would have to use a lower-level API or a different tool.

For most applications, sticking with PKCS8 and BestAvailableEncryption gives you a secure, portable result that works with modern systems.

python cryptography pem key serialization: Practical Usage a | RYUSLOG DEV