JavaScriptを有効にしてください

【PHP】openssl_error_string で OpenSSL のエラー内容を取得する

 ·  ☕ 4 分で読めます

【PHP】openssl_error_string() で OpenSSL のエラー内容を取得する

PHP で openssl_sign()openssl_verify()openssl_pkey_get_private() などの OpenSSL 系関数を使っていると、処理が false を返して失敗することがあります。

ただ、戻り値が false だけだと、何が原因で失敗したのか分かりません。

そんなときに使うのが openssl_error_string() です。
この関数を使うと、OpenSSL 内部で発生したエラー内容を文字列として取得できます。

openssl_error_string() とは

openssl_error_string() は、OpenSSL のエラーキューに積まれているエラーメッセージを 1 件ずつ取り出す関数です。

戻り値は次のとおりです。

  • エラーがある場合: string
  • これ以上エラーがない場合: false

PHP 公式マニュアル:
openssl_error_string - PHP Manual

ポイントは、1 回だけ呼べば終わりではないことです。
OpenSSL のエラーはキューに複数積まれることがあるため、通常は while で取り切ります。

基本的な使い方

OpenSSL 系の処理が失敗した直後に、次のようにエラーを回収します。

1
2
3
4
5
6
7
8
9
<?php

$privateKey = openssl_pkey_get_private($pem, $passphrase);

if ($privateKey === false) {
    while (($error = openssl_error_string()) !== false) {
        error_log($error);
    }
}

実務では error_log() にそのまま流すか、配列にためてまとめて記録する形が扱いやすいです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

$signature = '';
$result = openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);

if ($result === false) {
    $errors = [];

    while (($error = openssl_error_string()) !== false) {
        $errors[] = $error;
    }

    error_log('[OpenSSL] ' . implode(' | ', $errors));
}

なぜ while で読むのか

openssl_error_string() は最後のエラー 1 件だけを返す関数ではなく、キューから順番に取り出す関数です。

そのため、次のように空になるまで取得するのが基本です。

1
2
3
4
5
<?php

while (($error = openssl_error_string()) !== false) {
    echo $error, PHP_EOL;
}

特に、鍵や証明書の読み込みに失敗したときは、関連するエラーが複数件積まれることがあります。
1 件だけ見て判断すると、原因を見誤ることがあります。

よくある利用場面

openssl_error_string() は、次のような場面で役に立ちます。

  • 秘密鍵や公開鍵の読み込みに失敗したとき
  • PEM 形式の文字列が壊れているとき
  • パスフレーズが違うとき
  • 署名や検証に失敗したとき
  • 暗号化や復号に失敗したとき
  • 証明書や CSR の生成に失敗したとき

OpenSSL 系の API は失敗時に false しか返さないことが多いため、調査では openssl_error_string() がほぼ必須です。

エラーの具体例

実際には、環境によって次のようなエラー文字列が出ることがあります。

PEM の形式が不正なとき

1
error:0909006C:PEM routines:get_name:no start line

このエラーは、PEM 形式として読み込める開始行が見つからない場合によく出ます。

たとえば次のような原因が考えられます。

  • -----BEGIN PRIVATE KEY----- のようなヘッダがない
  • 改行が壊れている
  • PEM ではない文字列を渡している
  • 証明書を期待している箇所に秘密鍵を渡している

関連する例:
OpenSSL issue: no start line

別の実例:
cannot load certificate / no start line

復号に失敗したとき

1
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

このエラーは、復号に必要な条件がそろっていないときによく出ます。

  • パスワードが違う
  • 鍵が違う
  • IV が違う
  • 暗号化方式が一致していない
  • 暗号文が壊れている

実例:
bad decrypt の実行例

エラー文字列の見方

OpenSSL のエラーはおおむね次のような構造です。

1
error:[error code]:[library name]:[function name]:[reason string]

たとえば、

1
error:0909006C:PEM routines:get_name:no start line

であれば、主に見るべき箇所は次の 3 つです。

  • PEM routines
    • どの種類の内部処理で失敗したか
  • get_name
    • どの処理の途中で失敗したか
  • no start line
    • 何が問題だったか

実務では、まず最後の reason string を見るだけでも原因をかなり絞れます。

OpenSSL 公式の関連ドキュメント:

利用時の注意点

失敗した直後に読む

後続で別の OpenSSL 関数を呼ぶと、エラーキューの内容が変わってしまうことがあります。
エラー調査をしたいなら、失敗直後に回収した方が安全です。

ユーザーに詳細をそのまま見せない

openssl_error_string() の内容は調査には有用ですが、利用者向けのエラーメッセージとしては詳細すぎます。

画面には一般的なメッセージだけ出し、詳細はログに残す運用の方が無難です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php

if ($result === false) {
    $errors = [];

    while (($error = openssl_error_string()) !== false) {
        $errors[] = $error;
    }

    error_log('[OpenSSL] ' . implode(' | ', $errors));

    echo '署名処理に失敗しました。';
}

エラー文字列は環境差がある

同じコードでも、次の条件で表示内容が変わることがあります。

  • OpenSSL のバージョン
  • PHP のビルド差
  • OS やディストリビューション差

そのため、ネット上で見つけた文言と完全一致しなくても、bad decryptno start line のような理由部分が近ければ、同系統の問題である可能性が高いです。

まとめ

openssl_error_string() は、PHP で OpenSSL 関連処理の失敗原因を調べるための基本関数です。

押さえておきたいポイントは次のとおりです。

  • OpenSSL のエラー内容を文字列で取得できる
  • エラーはキューに積まれるので while で取り切る
  • 失敗した直後に読む
  • 詳細は画面表示ではなくログに残す
  • エラー文言は環境によって多少変わる

OpenSSL 周りは false だけでは原因が分かりにくいので、失敗時はまず openssl_error_string() を確認する癖をつけておくと調査がかなり楽になります。

参考

共有

こぴぺたん
著者
こぴぺたん
Copy & Paste Engineer