03 May 2009

(PHP) GnuPGを利用する

PHPでGnuPG (gpg) を用いる方法のメモ

PHPの標準関数proc_open( ) を用いて、gpgコマンドを直接呼び出す例。PearにCrypt_GPGライブラリが存在するが、「秘密鍵が無い、公開鍵だけの状態で暗号化できない」という不具合があるため、直接gpgコマンドを使うことにした。


プロセスを作成しUNIX/Linux上でプログラムを起動するためのコア関数
$cmdがシェルに渡すコマンドライン。 (gpgじゃなくても、どんなプログラムでも起動可能)
例 $cmd = '/usr/local/bin/gpg --list-keys';

function exec_gpg($cmd) { $descriptorspec = array( 0 => array('pipe', 'r'), // stdin 1 => array('pipe', 'w'), // stdout 2 => array('pipe', 'w') // stderr ); // プロセスを開始する(プログラムが実行される) $process = proc_open($cmd, $descriptorspec, $pipes); fclose($pipes[0]); // stdinを閉じる // stdoutの内容を全て読み込む $StdOut = ''; while(!feof($pipes[1])) { $StdOut .= fgets($pipes[1], 1024); } fclose($pipes[1]); // stdoutを閉じる // stderrの内容を全て読み込む while(!feof($pipes[2])) { $StdOut .= fgets($pipes[2], 1024); } fclose($pipes[2]); // stderrを閉じる // プロセスを終了する proc_close($process); return $StdOut; }

Webフォームでファイルのアップロードを行い、そのファイルをGnuPGで暗号化する関数。

Apacheの設定によってはシェル環境が引き継がれていないため、gpgコマンドを呼び出すときに、ユーザのホームディレクトリを指定しないとエラーになる場合がある。(--homedir スイッチで強制的にディレクトリを指定)
また、単純に公開鍵をインポートしただけでは、鍵の信頼性が設定されていないため、暗号化時にチェックに引っかかり暗号化出来ない場合がある。全ての鍵の信頼性チェックを無効化するため、 --trust-model always スイッチを付加する。

function upload_and_encrypt($FILES, $sKey) { $strReturn = ''; // 実行結果、エラー等を返す変数 $uploaddir = './upload/'; $uploadfile = $uploaddir . basename($FILES['userfile']['name']); if(!file_exists($FILES['userfile']['tmp_name'])) { $strReturn .= "ERROR:ファイルのアップロードに失敗しました\n"; return $strReturn; } if (!move_uploaded_file($FILES['userfile']['tmp_name'], $uploadfile)) { $strReturn .= "ERROR:アップロードファイルがTMPエリアから移動できません\n"; unlink($FILES['userfile']['tmp_name']); // 可能であればファイル削除 return $strReturn; } // シェルに引き渡すべきでない文字をエスケープする $strInputfile = escapeshellcmd($uploadfile); $sKey = escapeshellcmd($sKey); $strReturn .= "対象ファイル : ".$strInputfile."\n"; $strReturn .= "暗号化に用いる公開鍵 : ".$sKey."\n"; $cmd = "/usr/local/bin/gpg --homedir '/home/tmg1136-inue2/.gnupg' --trust-model always --textmode -vv -e -a -r ".$sKey." ".$strInputfile; // GnuPGを起動する $strResult = exec_gpg($cmd); // 元ファイルの削除 unlink($strInputfile); return $strReturn; }

暗号化されたファイルをダウンロードさせる、もしくは画面表示するための関数

function download_file($filename, $mode=0) { $strReturn = ''; // 暗号化ファイルは、.ascの拡張子を付ける $uploaddir = './upload/'; $filename = $uploaddir . basename($filename) . '.asc'; if(!file_exists($filename)) { $strReturn .= "ERROR:暗号化に失敗しています(ファイル出力されず)\n"; return $strReturn; } $fi = fopen($filename, "r"); if($fi == null) { $strReturn = "ERROR:暗号化ファイルは存在しますが、開くことが出来ません\n"; unlink($filename); // 可能であればファイルを削除しておく return $strReturn; } $strContents = ''; while(!feof($fi)) { $strContents .= fgets($fi, 1024); } fclose($fi); if($mode == 0) { // 画面表示 print("\t<pre>\n".htmlspecialchars($strContents)."</pre>\n"); } else { // ファイルのダウンロード header('Content-Disposition: attachment; filename="'.basename($filename).'"'); // IE 対策 http://support.microsoft.com/default.aspx?scid=kb;ja;436605 //header('Content-Disposition: inline; filename="'.$filename.'"'); header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); header('Content-Length: '.filesize($filename)); echo $strContents; unlink($filename); } return $strReturn; }

共通部分と、Webフォーム表示関数

<?php if(isset($_GET['download'])) { // 暗号化したファイルをダウンロードさせる場合の処理 $uploaddir = './upload/'; $filename = $uploaddir . basename($_GET['download']) . '.asc'; if(file_exists($filename)) { // ファイルが存在する場合のみ、ダウンロード処理を開始 download_file($_GET['download'], 1); exit(); } } ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Language" content="ja" /> <?php if(!empty($_FILES['userfile']['name']) && isset($_POST['key']) && !empty($_POST['key'])) { // 指定秒数経過後に、ダウンロード処理のURLへ自動転送するためのMETAタグ挿入 $_downloadfilename = $_FILES['userfile']['name']; print("\t<meta http-equiv=\"Refresh\" content=\"5; URL=https://www.example.com/index.php?download=$_downloadfilename\" />"); } ?> <title> </title> </head> <body> <?php // 言語と文字コードの設定 mb_language('Japanese'); mb_internal_encoding('UTF-8'); mb_http_output('UTF-8'); // このページのファイル名(リロード用) $strReloadPage = 'index.php'; print("<p>GPG-Webフロントエンド</p>\n"); if(!empty($_FILES['userfile']['name']) && isset($_POST['key']) && !empty($_POST['key'])) { // ファイルがアップロードと暗号化 $strResult = upload_and_encrypt($_FILES, $_POST['key']); // 暗号化のサマリーを表示 print(" <pre>\n".htmlspecialchars($strResult)."</pre> "); download_file($_FILES['userfile']['name']); // エンコード結果を画面表示 } else { // メニューページ(入力フォーム)を表示する display_form($strReloadPage); } exit(); function display_form($strReloadPage) { // ファイル アップロードのダイアログを表示する print(" <p>ファイルを暗号化する</p> <form enctype=\"multipart/form-data\" action=\"$strReloadPage\" method=\"POST\"> <table border=\"0\" cellpadding=\"2\" cellspacing=\"0\" align=\"center\" width=\"700px\"> <tr><td>公開鍵</td><td><input name=\"key\" size=\"40\" /></td></tr> <!-- アップロードするファイルのサイズ上限設定 --> <input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"30000\" /> <tr><td>対象ファイル</td><td><input name=\"userfile\" type=\"file\" size=\"40\" /></td></tr> <tr><td></td><td><input type=\"submit\" value=\"暗号化実行\" /></td></tr> </table> </form> "); return; }