2011/06/15

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

php ネタ

openssl (1) で暗号化されたファイルを復号化する処理を php で実装する必要があった。
pear を探せばその手のモジュールは多分あるだろうと思ったが、 標準で含まれないモジュールに依存したくなかったので 外部コマンドの openssl を利用する方法を試してみた。

php では入力、もしくは出力のみの外部コマンド実行は popen() を利用できるが、 入出力双方向が必要なので proc_open() を利用する。
最初は proc_open() した openssl の入力側パイプに 暗号化されたデータを全て出力 (fwrite()) した後で 出力側パイプから復号化されたデータを読み出し (fread()) ていたのだが、 入力データがある程度のサイズを越えると openssl はデータ読込みの途中でデータを出力しないと 読込み (もしくは処理) をブロックしてしまう様なので、 fwrite() の後に fread() を実行する様に修正した。
この時、当初は stream_select() を利用してパイプの出力側からの 入力可否を監視する様にしたのだが、 stream_set_bloking() を利用してパイプの出力側を 非ブロックモードにする事で、 パイプから読込めない場合でも fread() が即リターンするので stream_select() の呼出しによりコードが煩雑になる事が避けられた。

  1<?php
  2    /*
  3     * Copyright (c) 2011 Mitzyuki IMAIZUMI. All rights reserved.
  4     *
  5     * $Id: decrypt.php 3 2011-06-16 16:15:48Z mitz $
  6     */
  7
  8    define("OPENSSL",    "openssl enc -d -des3 -pass pass:%s");
  9    define("BLOCSIZE",   "4096");
 10
 11    /*
 12     * ファイルの復号化
 13     *   $1: ファイル名
 14     *   $2: サイズ
 15     *   $3: パスフレーズ
 16     */
 17    function    decrypt($file, $size, $pass)
 18    {
 19
 20        $desc = array(
 21            0 => array("pipe", "r"),                /* stdin:  pipe */
 22            1 => array("pipe", "w"),                /* stdout: pipe */
 23            2 => array("file", "/dev/null", "w")    /* stderr: /dev/null */
 24        );
 25
 26        if(($fp = fopen($file, "r+"))){
 27            if($data = fread($fp, $size)){
 28                if(preg_match("/^Salted_/", $data)){
 29                    /* 暗号化されている場合 */
 30                    if($pp = proc_open(sprintf(OPENSSL, $pass), $desc, $pipe)){
 31                        /*
 32                         * `openssl enc -d …' を実行する。
 33                         *
 34                         * ファイルの内容はすでに $data に格納されているので
 35                         * BLOCSIZE 単位で openssl の標準入力に出力する。
 36                         */
 37
 38                        stream_set_write_buffer($pipe[0], 0);
 39                        stream_set_blocking($pipe[1], 0);
 40
 41                        $buf = "";
 42                        
 43                        while($size > 0){
 44                            /*
 45                             * 1 ブロック出力
 46                             * substr() は開始位置に負の値を指定すると
 47                             * 文字列の終端を起点とした開始位置からの
 48                             * 部分文字列が取得できる。
 49                             */
 50                            fwrite($pipe[0], substr($data, 0 - $size, BLOCSIZE));
 51                            $size -= BLOCSIZE;
 52                            /*
 53                             * openssl からの読み出し処理
 54                             * 非ブロッキングなので、
 55                             * 読めない場合は即座に fread() から戻る。
 56                             *
 57                            $buf .= fread($pipe[1], BLOCSIZE);
 58                        }
 59                        fclose($pipe[0]);
 60
 61                        while(!feof($pipe[1]))
 62                            $buf .= fread($pipe[1], BLOCSIZE);
 63                        fclose($pipe[1]);
 64
 65                        proc_close($pp);
 66                    }
 67                    /*
 68                     * 復号化したデータの出力
 69                     * 入力ファイルを書き換える
 70                     */
 71                    fseek($fp, 0);
 72                    ftruncate($fp, 0);
 73                    fwrite($fp, $buf);
 74                }
 75            }
 76            /* 暗号化されていない場合はそのまま close() する */
 77            fclose($fp);
 78        }
 79
 80    }
 81
 82?>
    

2011/06/16 追記

ブロック転送のロジックを整理して最適化した。
データサイズ $size をループの制御変数とする事で 余計な変数や転送ブロック数の計算を削除し、 openssl への出力データの部分文字列切り出し処理で substr() を利用する際に負の値を指定して 開始位置を文字列後端からの位置で指定する様に変更した。


Copyright © 2008-2020 Mitzyuki IMAIZUMI. All rights reserved.