Mensaje de error
Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. MalformedFraming
La frase que precede al token es fija; el token indica qué falló. MalformedFraming significa que no se encontraron las líneas BEGIN/END; MismatchedTags("PRIVATE KEY", "PUBLIC KEY") significa que no coinciden; InvalidData(Invalid symbol 92, offset 0.) significa que el cuerpo no es base64: 92 es una barra invertida, la marca de las secuencias \n literales.
Python cryptography — ValueError from load_pem_* functions
Qué significa
load_pem_private_key / load_pem_public_key / load_pem_x509_certificate no encontraron una delimitación PEM intacta: líneas BEGIN y END que coincidan, con un cuerpo base64 entre ellas. El token del final (aquí, MalformedFraming) es el diagnóstico del analizador; el enlace a las preguntas frecuentes viene dentro del propio texto del error.
Por qué ocurre
PEM truncado o copiado a medias
frecuenteSi falta la línea END (el portapapeles cortó el texto, un bloque escalar de YAML se comió la última línea), la delimitación se rompe. Todo PEM debe cerrarse con su línea -----END ...----- correspondiente. Un valor vacío —un secreto sin definir— produce el mismo MalformedFraming.
Saltos de línea escapados en variables de entorno o configuración
frecuenteLos PEM entregados mediante archivos .env, docker-compose, manifiestos de Kubernetes o JSON suelen llegar en una sola línea con secuencias \n literales. El cargador notifica la misma frase con InvalidData(Invalid symbol 92, offset 0.) al final.
Marcadores BEGIN o END dañados
ocasionalUn marcador al que le falta un guion, o con guiones de aspecto parecido pegados desde un editor de documentos, ya no delimita el bloque; una etiqueta END distinta de la etiqueta BEGIN se notifica como MismatchedTags. Lo que cryptography 50 tolera: comillas alrededor del bloque, sangría de YAML, un BOM UTF-8 y finales de línea CRLF.
Paquete concatenado con un separador dañado
raraLos archivos de cadena con varios bloques PEM fallan por completo cuando un bloque perdió su línea END; entonces el cargador notifica InvalidData(Invalid symbol 45, …), donde 45 es el primer guion de la siguiente línea BEGIN. El cargador señala el archivo, no el bloque, así que busca por bisección: divide el paquete y carga cada objeto por separado para encontrar el dañado.
Cómo solucionarlo
- 1.
Normalizar antes de cargar
Restaura los saltos de línea, elimina los espacios en blanco y las comillas, y comprueba la primera línea antes de pasar los bytes al cargador.
python import os pem = os.environ["PRIVATE_KEY"].replace("\\n", "\n").strip().strip('"').encode() assert pem.startswith(b"-----BEGIN"), pem[:30] from cryptography.hazmat.primitives.serialization import load_pem_private_key key = load_pem_private_key(pem, password=None) - 2.
Preferir archivos o secretos multilínea nativos
Los archivos de secretos montados evitan por completo el problema del escapado; los Secrets de Kubernetes y la mayoría de los gestores de secretos pueden entregar el PEM exacto byte a byte.
python with open("/run/secrets/signing_key.pem", "rb") as f: key = load_pem_private_key(f.read(), password=None) - 3.
Validar la delimitación de forma mecánica
Una comprobación rápida de que las líneas BEGIN/END existen y coinciden permite saber si el fallo está en el contenido o en el transporte.
python lines = pem.decode().splitlines() print(lines[0]) # -----BEGIN PRIVATE KEY----- print(lines[-1]) # -----END PRIVATE KEY-----