错误信息
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()` 获取。
原因
来自环境变量的 PEM 带有字面量 \n
常见存放在 `.env` 文件、CI 变量或 JSON 中的密钥,常常用 `\` 和 `n` 这两个字符代替换行符。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);