エラーメッセージ
openssl_verify(): Supplied key param cannot be coerced into a public key
別の表記
- openssl_sign(): Supplied key param cannot be coerced into a private key
- Warning: openssl_verify(): Supplied key param cannot be coerced into a public key
関数名(`openssl_verify`、`openssl_sign`、`openssl_seal`、`openssl_public_encrypt` など)と鍵の種類(「public key」/「private key」)は、状況によって変わります。`display_errors` が有効な場合、PHP は先頭に「Warning:」を付けます。
PHP OpenSSL extension — warning from openssl_verify / openssl_sign / openssl_seal
このエラーの意味
PHP の OpenSSL 関数は、鍵を PEM のテキスト、`file://` のパス、鍵オブジェクトのいずれかで受け取りますが、渡された値を必要な種類の鍵(検証なら公開鍵、署名なら秘密鍵)に変換できませんでした。これは例外ではなく警告です。関数は `false`(または `-1`)を返すため、`=== 1` だけをチェックしているコードは、何も通知せずにこれを「署名が無効」として扱います。根本にある OpenSSL の理由は、`openssl_error_string()` で取得できます。
発生する理由
環境変数から来た、文字としての \n を含む PEM
よくある`.env` ファイル、CI の変数、JSON に保存された鍵には、改行の代わりに `\` と `n` の 2 文字が含まれていることがよくあります。OpenSSL はこのような PEM をパースできません。
file:// のないファイルパス
よくある`/etc/app/private.pem` のようなパスだけを渡すと、PEM のテキストとして扱われて失敗します。PHP には `file:///etc/app/private.pem` か、ファイルの内容を渡す必要があります。
公開鍵が必要な箇所に証明書、秘密鍵、SSH キーを渡している
ときどき`openssl_verify` は証明書か公開鍵を受け付けます。OpenSSH の公開鍵(`ssh-ed25519 AAAA…`)や JWK の JSON 文字列は PEM ではありません。
暗号化された秘密鍵をパスフレーズなしで渡している
ときどき`ENCRYPTED PRIVATE KEY` の PEM を文字列のまま `openssl_sign` に渡しても機能しません。先に `openssl_pkey_get_private($pem, $passphrase)` で読み込んでください。
修正方法
- 1.
鍵を明示的に読み込み、OpenSSL の理由を表に出す
`openssl_pkey_get_public` / `_private` は失敗すると `false` を返します。`openssl_error_string()` をエラーがなくなるまで呼び出して原因をログに記録し、検証に失敗した状態のまま処理を続けずに中止してください。
php $pem = str_replace('\\n', "\n", getenv('JWT_PUBLIC_KEY')); // undo escaped newlines $publicKey = openssl_pkey_get_public($pem); if ($publicKey === false) { while ($msg = openssl_error_string()) { error_log($msg); // the real OpenSSL reason } throw new RuntimeException('JWT_PUBLIC_KEY is not a usable public key'); } $ok = openssl_verify($data, $signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; - 2.
ファイルパスには file:// を使い、パスフレーズを渡す
鍵の取得元を明示してください。PEM の内容か `file://` の URI のどちらかを指定し、暗号化された鍵ではパスフレーズも渡します。
php // Paths need the file:// scheme; encrypted keys need their passphrase. $privateKey = openssl_pkey_get_private('file:///etc/app/private.pem', getenv('KEY_PASSPHRASE') ?: null);