エラーメッセージ
Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. MalformedFraming
トークンの前の文は固定で、トークンが問題の内容を示します。MalformedFraming は BEGIN/END 行が見つからなかったこと、MismatchedTags("PRIVATE KEY", "PUBLIC KEY") はそれらが一致しないこと、InvalidData(Invalid symbol 92, offset 0.) は本文が base64 ではないことを意味します。92 はバックスラッシュで、文字としての \n の並びがあることを示しています。
Python cryptography — ValueError from load_pem_* functions
このエラーの意味
load_pem_private_key / load_pem_public_key / load_pem_x509_certificate は、壊れていない PEM のフレーミング、つまり対応する BEGIN 行と END 行の間に base64 の本文がある構造を見つけられませんでした。末尾のトークン(ここでは MalformedFraming)はパーサーによる診断です。FAQ へのリンクは、エラーメッセージ自体に含まれています。
発生する理由
途中で切れた、または一部だけコピーされた PEM
よくあるEND 行が欠けていると(クリップボードの内容が途中で切れた、YAML のブロックスカラーが最終行を飲み込んだなど)、フレームが壊れます。どの PEM も、対応する -----END ...----- 行で終わっていなければなりません。空の値(設定されていないシークレット)でも、同じ MalformedFraming になります。
環境変数や設定ファイルに由来するエスケープされた改行
よくある.env ファイル、docker-compose、Kubernetes のマニフェスト、JSON を経由して渡される PEM は、文字としての \n を含む 1 行になっていることがよくあります。この場合、ローダーは同じ文の末尾に InvalidData(Invalid symbol 92, offset 0.) を付けて報告します。
BEGIN または END マーカーの損傷
ときどきダッシュが 1 つ欠けたマーカーや、文書エディターから貼り付けた見た目の似たダッシュでは、ブロックの区切りとして機能しなくなります。BEGIN のラベルと異なる END のラベルは、MismatchedTags として報告されます。cryptography 50 が許容するのは、ブロックを囲む引用符、YAML のインデント、UTF-8 の BOM、CRLF の改行です。
区切りが壊れた連結バンドル
まれ複数の PEM ブロックを含むチェーンファイルは、1 つのブロックの END 行が失われると全体が失敗します。その場合、ローダーは InvalidData(Invalid symbol 45, …) を報告します。45 は次の BEGIN 行の最初のダッシュです。ローダーが報告するのはファイル単位であり、ブロック単位ではありません。そのため、二分探索で切り分けてください。バンドルを分割して各オブジェクトを個別に読み込めば、壊れたものを見つけられます。
修正方法
- 1.
読み込む前に正規化する
バイト列をローダーに渡す前に、改行を復元し、余分な空白と引用符を取り除き、先頭行を確認してください。
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.
ファイルか、複数行をそのまま扱えるシークレットを使う
シークレットをファイルとしてマウントすれば、エスケープの問題を完全に回避できます。Kubernetes の Secret やほとんどのシークレット保管庫は、PEM をバイト単位で正確に渡せます。
python with open("/run/secrets/signing_key.pem", "rb") as f: key = load_pem_private_key(f.read(), password=None) - 3.
フレーミングを機械的に検証する
BEGIN/END 行が存在して一致しているかを手早く確認すれば、問題が内容にあるのか受け渡しにあるのかを絞り込めます。
python lines = pem.decode().splitlines() print(lines[0]) # -----BEGIN PRIVATE KEY----- print(lines[-1]) # -----END PRIVATE KEY-----