php の proc_open() を利用した openssl コマンドの実行


今更ながらの php4 ネタです。php5 では動作を確認していません。というか php5 使った事すらありません。

*openssl* (1) で暗号化されたファイルを復号化する処理の php4 での実装。
pear を探せばその手のモジュールは多分あるだろうと思ったのですが、 諸処の事情により外部コマンドの *openssl* (1) を利用する方法を試してみました。

php4 では入力、もしくは出力のみの外部コマンド実行は **popen()** を利用できますが、 入出力双方向が必要なので **proc_open()** を利用します。最初は **proc_open()** した *openssl* (1) の入力側パイプに暗号化されたデータを全て **fwrite()** で出力した後で、出力側パイプから復号化されたデータを **fread()** で読み出していたのですが、 php4 の仕様なのか *openssl* (1) の仕様なのか、入力データがある程度のサイズを越えるとデータを出力しないと読込み (もしくは処理) をブロックしてしまう様なので、**fwrite()** の後に **fread()** を実行する様に修正した。

この時、当初は **stream_select()** を利用してパイプの出力側からの入力可否を監視する様にしたのですが、**stream_set_bloking()** を利用してパイプの出力側を非ブロックモードにする事でパイプから読込めない場合でも **fread()** が即リターンするので **stream_select()** が不要になりコードが煩雑になる事が避けられた。

```php:decrypt.sh
<?phpde

	/* openssl コマンド */
    define("OPENSSL",    "openssl enc -d -des3 -pass pass:%s");
	/* ブロックサイズ */
    define("BLOCSIZE",   "4096");

    /*
     * ファイルの復号化
     *   $1: ファイル名
     *   $2: サイズ
     *   $3: パスフレーズ
     */
    function    decrypt($file, $size, $pass)
    {

        $desc = array(
            0 => array("pipe", "r"),                /* stdin:  pipe */
            1 => array("pipe", "w"),                /* stdout: pipe */
            2 => array("file", "/dev/null", "w")    /* stderr: /dev/null */
        );

        if(($fp = fopen($file, "r+"))){
            if($data = fread($fp, $size)){
                if(preg_match("/^Salted_/", $data)){
                    /* 暗号化されている場合 */
                    if($pp = proc_open(sprintf(OPENSSL, $pass), $desc, $pipe)){
                        /*
                         * `openssl enc -d …' を実行する。
                         *
                         * ファイルの内容はすでに $data に格納されているので
                         * BLOCSIZE 単位で openssl の標準入力に出力する。
                         */

                        stream_set_write_buffer($pipe[0], 0);
                        stream_set_blocking($pipe[1], 0);

						/* 出力バッファ初期化 */
                        $buf = "";
                        
                        while($size > 0){
                            /*
                             * 1 ブロックを openssl コマンドに出力
                             * substr() は開始位置に負の値を指定すると
                             * 文字列の終端を起点とした開始位置からの
                             * 部分文字列が取得できる。
                             */
                            fwrite($pipe[0], substr($data, 0 - $size, BLOCSIZE));
                            $size -= BLOCSIZE;
                            /*
                             * openssl からの読み出し処理
                             * 非ブロッキングなので、
                             * 読めない場合は即座に fread() から戻る。
                             */
                            $buf .= fread($pipe[1], BLOCSIZE);
                        }
                        fclose($pipe[0]);

						/* 残りのブロック読み出し */
                        while(!feof($pipe[1]))
                            $buf .= fread($pipe[1], BLOCSIZE);
                        fclose($pipe[1]);

                        proc_close($pp);
                    }
                    /*
                     * 復号化したデータの出力
                     * 入力ファイルを書き換える
                     */
                    fseek($fp, 0);
                    ftruncate($fp, 0);
                    fwrite($fp, $buf);
                }
            }
            /* 暗号化されていない場合はそのまま close() する */
            fclose($fp);
        }

    }

?>
```