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() を利用する際に負の値を指定して 開始位置を文字列後端からの位置で指定する様に変更した。