• PHP
【php8上級/準上級試験】模擬問題解説 問題29 openssl_encrypt() openssl_decrypt()

「第1回 PHP 8 上級 模擬試験」を解説していきます

本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!

第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/

解説記事一覧

模擬問題 29

関数 に関する説明の中で、誤っているものを1つ選びなさい。
また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。

declare(strict_types=1);
error_reporting(-1);
下記はマニュアルから一部引用した内容である。

openssl_encrypt ( string $data , string $method , string $key , int $options = 0 , string $iv = “” , string &$tag = NULL , string $aad = “” , int $tag_length = 16 ) : string|false
openssl_decrypt ( string $data , string $method , string $key , int $options = 0 , string $iv = “” , string $tag = “” , string $aad = “” ) : string|false
openssl_x509_parse ( mixed $x509cert , bool $shortnames = true ) : array

openssl_get_cipher_methods() 関数は利用可能な暗号メソッドを取得する。

そのため、以下のコード

var_dump( openssl_get_cipher_methods() );

を実行すると、結果は次のとおりとなる。

array(201) {
  [0]=>
  string(11) "AES-128-CBC"
  [1]=>
  string(21) "AES-128-CBC-HMAC-SHA1"
  [2]=>
  string(23) "AES-128-CBC-HMAC-SHA256"
(中略)
  [199]=>
  string(8) "seed-ecb"
  [200]=>
  string(8) "seed-ofb"
}

この関数は、現在の環境で利用可能な暗号化アルゴリズム(暗号化方式)の一覧を取得するための関数

$methods = openssl_get_cipher_methods();
print_r($methods);


出力例(一部):
Array
(
    [0] => AES-128-CBC
    [1] => AES-192-CBC
    [2] => AES-256-CBC
    [3] => BF-CBC
    ...
)

問題文の内容は正しい⭕です

openssl_encrypt() 関数はデータを暗号化する。

そのため、以下のコード

$method = 'AES-128-CBC';
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));
$crypt = openssl_encrypt('exam', $method, 'key', 0, $iv);
var_dump( $crypt );

string(24) “GoaKntvUhPOEl5VS92Uiyg==”
となる ( initialization vector が random なので、値は実行毎に変わる)。

なお、例えば CBC のような「initialization vector が必要な暗号利用モード」で initialization vector を指定しないと Warning が出る。

そのため以下のコード

$method = 'AES-128-CBC';
$crypt = openssl_encrypt('exam', $method, 'key', 0);
var_dump( $crypt );

を実行すると、結果は次のとおりとなる。

Warning: openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended in …
string(24) “5kBbn5hweqDYYT6VQjj2ag==”

openssl_encrypt()関数

openssl_encrypt()関数はデータを暗号化するために使われます

各引数の意味

openssl_encrypt(
    string $data,     // 🔸1. 暗号化するデータ(電話番号など)
    string $cipher_algo, // 🔸2. 使用する暗号方式(AES-256-CBC など)
    string $passphrase,  // 🔸3. 鍵(共通鍵方式の場合は同じ鍵で復号もする)
    int $options = 0,     // 🔸4. オプション(基本は 0 でOK)
    string $iv = "",      // 🔸5. 初期化ベクトル(CBCモードでは必須!)
    string &$tag = null,  // 🔹6. GCM/CCM モード時の認証タグ(CBCでは使わない)
    string $aad = "",     // 🔹7. AAD(追加認証データ、主にGCM用)
    int $tag_length = 16  // 🔹8. 認証タグの長さ(GCM用)
)

初期化ベクトル(IV)とは?

暗号化のとき、毎回違う「スタート地点」や「かく乱種」を与えるデータのこと!

暗号方式(特に CBC, GCM など)では、同じデータを毎回同じ結果にしちゃうと意味ないから、
暗号結果を変えるための「ランダムな要素」として使うのが IV(Initialization Vector)!

暗号化方式としてCBCのようなIV(初期化ベクトル)が必要なモードを使用する場合、IVを指定しないとWarningが表示されます

CBC以外の暗号化モード

PHPで選ぶなら

条件モード理由
手軽に暗号化したい(ログ用など)CBCPHPの定番、実装楽ちん
高セキュリティ(改ざんNG)GCM認証付き、安全性◎
古い記事を見てなんとなく使ってるCBC多くのチュートリアルがCBC前提
パターンがバレたらヤバい情報GCMECBは論外、CBC/GCMどちらかにして

よって選択肢の内容は正しい⭕です

実際のopenssl_encrypt()使用例

// 暗号化に使用する共通鍵(32文字:AES-256-CBC 用)
// ※セキュリティの観点から、実際は .env ファイルなどから取得するのがベスト!
$key = 'your-32-char-secret-key-123456789012';
$cipher = 'AES-256-CBC'; // 使用する暗号方式。ここでは AES-256-CBC を使用。

// 電話番号が存在する場合のみ暗号化処理を行う
if (isset($post_data['tel'])) {
    // 初期化ベクトル(IV)を暗号方式に合わせてランダムに生成
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));

    // 電話番号を暗号化。戻り値は base64 エンコードされていない暗号テキスト
    $encrypted_tel = openssl_encrypt($post_data['tel'], $cipher, $key, 0, $iv);

    // IV と暗号化されたデータを `:` で連結し、ログ用に1つの文字列としてまとめる
    // 復号時に両方必要になるため、この形式にしておく
    $post_data['tel'] = base64_encode($iv):IVはバイナリなので、そのままではログ出力に不向き。Base64化がベター。

':' 区切り:復号するときに explode(':', $data) で IV と暗号文を分離しやすくなる。e($iv) . ':' . $encrypted_tel;
}

// ログ出力:URL、暗号化済みの POST データ、エラー内容を JSON 形式で出力
// 電話番号は暗号化されているため、ログから個人情報が漏れる心配がない
error_log('ReCaptcha CURL Error: ' . json_encode([
    'url' => $url,
    'post_data' => $post_data,
    'error' => $error,
]));

  • base64_encode($iv):IVはバイナリなので、そのままではログ出力に不向き。Base64化がベター。
  • ‘:’ 区切り:復号するときに explode(‘:’, $data) で IV と暗号文を分離しやすくなる。

openssl_decrypt() 関数はデータを復号する。

そのため、以下のコード

$method = 'AES-128-CBC';
$key = 'key';
$crypt = openssl_encrypt('exam', $method, $key, 0, openssl_random_pseudo_bytes(openssl_cipher_iv_length($method)));
var_dump( $crypt );
$decrypt_string = openssl_decrypt($crypt, $method, $key);
var_dump( $decrypt_string );

を実行すると、結果は次のとおりとなる。

string(24) “MWFlUhetizqXeV321jVUvA==”
string(4) “exam”
となる。

initialization vector が random なので $crypt は実行毎に変わるが、復号された「exam」は常に同じになる。

また initialization vector は $crypt の中に含まれているため、引数として与えなくても正しく復号される。

✅ openssl_decrypt() は openssl_encrypt() で暗号化されたデータを復号する関数ですが、IV (Initialization Vector) が必要な暗号モードでは、IV なしでは正しく復号できません。

openssl_encrypt() の中で openssl_random_pseudo_bytes() を使って ランダムなIVを作成しているが、それが復号時に再利用されていない。

openssl_decrypt() に適切なIVを渡さないと、復号に失敗するか、異常なデータになる。
「IV は $crypt に含まれている」 という説明が誤り

よって問題文は誤り❌です

openssl_x509_parse() 関数は X509 証明書をパースし、配列として情報を返す。

そのため、以下のコード

$resource = @stream_socket_client(
    'ssl://www.phpexam.jp:443',
    $errno,
    $errstr,
    60,
    STREAM_CLIENT_CONNECT,
    stream_context_create(['ssl' => ['capture_peer_cert' => true]]),
);
$cont = stream_context_get_params($resource);
$x509 = openssl_x509_parse($cont['options']['ssl']['peer_certificate']);
var_dump($x509);

を実行すると、結果は次のとおりとなる。

array(16) {
["name"]=>
string(18) "/CN=www.phpexam.jp"
["subject"]=>
array(1) {
["CN"]=>
string(14) "www.phpexam.jp"
}
["hash"]=>
string(8) "491a204d"
["issuer"]=>
array(3) {
["C"]=>
string(2) "US"
["O"]=>
string(13) "Let's Encrypt"
["CN"]=>
string(2) "R3"
}
["version"]=>
int(2)
(後略)

PHP strpos() 関数の動作 0 1 2 3 4 5 6 7 8 9 10 P H P P r o g r a m s 例1: strpos($string, ‘P’) 検索: ‘P’ 最初に見つかる位置: 0 (先頭) 例2: strpos($string, ‘P’, 1) 検索: ‘P’(インデックス1から検索開始) 見つかる位置: 2 (3番目の文字) 注意: if(strpos(…) == false) は先頭 (0) で誤判定!

この説明に誤りはありません。内容は⭕です

問題番号正解の選択肢
1PHPの「最新以外の(古い)コード」は、公式サイトでの提供は全くしていない。そのため、古いバージョンのコードが必要な場合、別途「非公式の外部サイト」からソースコードを入手する必要がある。
2論理型 (boolean) は「真偽値」とも呼ばれ、値は true か false か null のいずれかになる。なお、true、false、null の文字は、大文字でも小文字でもよい。
3抽象クラスから継承する際、親クラスで abstract としてマークされた全てのメソッドは子クラスで定義する必要があり、可視性は同等(またはより緩い制約)で、必須引数の数は同じであれば型宣言が異なってもかまわない。
4PHP において、デストラクタは __destruct() メソッドで実装される。親クラスのデストラクタは暗黙的に呼ばれ、呼び出し順序は「子クラスのデストラクタ → 親クラスのデストラクタ」となる。
5__get() はアクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用される。なお、__getStatic() は存在せず、オブジェクトや静的コンテキストで動作しない。
6PHP のリファレンス渡しを使用すると、関数内で引数を修正できる。(※※正解テキストでは「呼び出す側で変数に & を付ける必要がある」と記載されています。)
7名前空間は namespace キーワードで宣言する。通常はファイルの先頭に記述する必要があり、名前空間宣言前に書かれたクラスはその名前空間に含まれない。
8PHP 5 では Exception クラスが全例外の基底クラスだったが、PHP 7 以降は Exception クラスは Throwable インタフェースを基底とし、Error クラスが内部エラーの基底クラスとして導入された。
9ArrayAccess インターフェイスは、オブジェクトを配列としてアクセスするための機能を提供する。
10SplFileInfo クラスは、ファイルの情報取得や操作を行うためのクラスである。
11$_COOKIE は、HTTP クッキーから渡された値が連想配列として格納され、また設定も可能である。
12PHP 7.1.x では、文字列操作関数で負のオフセット指定が可能となり、[] や {} による文字単位のアクセスも文字列の末尾からのオフセットとして解釈される。
13可変変数は、スーパーグローバル変数にも使用できる。
14PHP 7.4.x では、波括弧を使った配列や文字列のオフセットアクセスの文法は非推奨となった。
15PHP 8 では、private メソッドの継承に関するルールが変更され、親クラスの同名メソッドの可視性に関係なく子クラスでオーバーライド可能となった。
16break は、現在実行中の for, foreach, while, do-while, switch 構造の実行を終了し、オプションでネストしたループの何段分を抜けるか指定できる。
17XSS 対策として、htmlentities() を適切に使用することで、文字列や配列の入力に対して安全な出力が可能となる。
18アップロードされたファイルの元のファイル名は、$_FILES[‘{formのnameの値}’][‘name’] に格納され、move_uploaded_file() の第二引数として利用できる。
19uniqid() と mt_rand() を組み合わせることで、暗号論的にランダムな(推測困難な)トークンを生成できる。
20PHP のセッションでは、セッション ID がクッキーに保存され、session_set_cookie_params() によりそのクッキーのパラメータを設定できる。
21PHP の変数の参照カウントは、xdebug_debug_zval() で確認でき、オブジェクトを clone した場合は内部的に参照が使われ、一時的に参照カウントが増加する。
22DirectoryIterator クラスは、ディレクトリ内のファイルやサブディレクトリの情報を取得するシンプルなインターフェイスを提供する。
23mail() 関数は、メールを送信するための関数で、第四引数で追加のヘッダー情報を指定できる。
24escapeshellarg() と escapeshellcmd() は、外部入力をシェルコマンドの引数として使用する際に必要なエスケープ処理を行い、どちらを使用しても意味合いは同じである。
25stream_wrapper_register() 関数を使用すると、新しいストリームラッパー(プロトコルハンドラ)を登録できるが、既に存在する場合は失敗する。
26strpos() 関数は、文字列内で指定した部分文字列の最初の出現位置を返し、見つからなければ false を返す。ただし、先頭位置の場合は 0 が返るため注意が必要。
27Phar のスタブには __HALT_COMPILER() が使用され、これ以降のコードはコンパイルされない。
28function_exists() 関数は、指定された関数が定義されているかどうかをチェックし、存在すれば true を返す。
29openssl_decrypt() 関数は、openssl_encrypt() で暗号化されたデータを正しく復号し、元のデータを取り戻す。
30strtotime() 関数は、英語形式の日付文字列を Unix タイムスタンプに変換し、無効な日付の場合は false を返す。