はじめに
理想的な世の中では、「暗号化」や「セキュリティ」といった言葉は存在すらしないでしょう。しかし現実の世界は完全とはほど遠いものです。ですから、ソフトウェア開発者はアプリケーションのセキュリティ対策に相応の時間をかけなければなりません。暗号化はSSL/TLS、証明書、デジタル署名などと同様、セキュリティというパズルの1ピースにすぎません。この記事では、PHPを使って最も一般的な暗号化アルゴリズムを実装する方法を説明します。PHPデフォルトの暗号化機能に加えて、多様な暗号化ライブラリやパッケージの使用法を幅広く紹介していきます。
この記事のサンプルコードでは、textfile.txtという小さなテキストファイルを使用します。このファイルには次のようなプレーンテキストが含まれています。
For every difficult and complicated
question there is an answer
that is simple, easily understood,
and wrong. H.L. Mencken
PHPデフォルトの暗号化関数
PHPには3つの暗号化関数、
md5()、
crypt()、
sha1()が用意されています。
md5()関数のプロトタイプは次のとおりです。
string md5(string $str [, bool $raw_output ])
この関数はMD5メッセージダイジェストアルゴリズムを使い、与えられた文字列のMD5ハッシュを計算します。
$str引数は、暗号化される文字列を表します。
$raw_output引数に
FALSE(デフォルト)を渡すと、関数は32文字の16進数を返します。
TRUEを渡すと、16バイトのバイナリ値そのものを返します。
PHPの
crypt()関数は、一方向の暗号化を行う関数です。この関数を使用すると、入力したパスワードが、保存されている暗号化済みパスワードと一致するかどうかを簡単に確認できます(暗号化済みパスワードを復号する手間が省けます)。
crypt()関数のプロトタイプは次のとおりです。
string crypt (string $str [, string $salt ])
この関数は、UNIX標準のDESベースの暗号化アルゴリズム(または各システムで使用できる代替アルゴリズム)を使って、暗号化した文字列を返します。
$str引数は、暗号化される文字列です。オプションの
$salt引数は、暗号化のベースとなる文字列(ソルト)です。ソルト文字列を指定しない場合、PHPは関数呼び出しごとにランダムにソルト文字列を生成します。
PHPの
sha1()関数は、文字列のSHA-1ハッシュを計算します。
sha1()関数のプロトタイプは次のとおりです。
string sha1 (string $str [, bool $raw_output ])
この関数はSHA-1ハッシュを文字列として返します。繰り返しになりますが、
$str引数は入力文字列を表します。オプションの
$raw_output引数に
TRUEを設定すると、関数はバイナリ形式そのままのSHA-1ハッシュを20文字分の長さで返します。
FALSEを設定すると、16進数表記で40文字分を返します。
実際の例を見てみましょう。以下のコードは、PHPデフォルトの暗号化関数を使ってtextfile.txtの内容を暗号化し、その結果をencrypted.txtファイル(図1を参照)に出力する方法を示しています。
図1 暗号化されたファイル。exrypted.txtファイルには、PHPデフォルト関数のmd5、crypt、sha1で暗号化したパスワードが入っています。
<?php
$file = 'textfile.txt';
$initial_contents = file_get_contents($file);
if($initial_contents){
$password = 'OctaviaAnghel';
//Calculates the md5 hash
$md5_data = md5($password);
//This function encrypts data
$crypt = crypt($password);
//Calculate the sha1 hash
$sha1 = sha1($password);
$encrypted_file = @fopen('encrypted.txt','w');
$ok_encrypt
= @fwrite($encrypted_file,'md5: '. $md5_data."\r¥".'crypt:
'.$crypt."\r¥".'sha1: '.$sha1);
if($ok_encrypt){
echo 'The encrypted code was succesfully created'.
' in encrypted_file.txt!!!'.'
';
}
else{
echo ("The write of this file failed!");
}
@fclose($encrypted_file);
}
?>
PHPはこれらのデフォルトの関数に加え、外部ライブラリおよびパッケージを介した暗号化にも対応しています。表1に、以降で述べるライブラリおよびパッケージを示します。
表1 PHPでの暗号化と復号に使われるパッケージおよびライブラリ
MCryptを用いた大規模データの暗号化
MCryptを使用すると、開発者はさまざまな暗号化関数を用いてファイルやデータストリームを暗号化できます。暗号に関する専門知識は必要ありません。MCryptは、Blowfish、DES、TripleDES、SAFER-SK128、TWOFISH、TEA、RC2、3-WAY、SAFER-SK64などのブロックアルゴリズムと、いくつかの「利用モード」(modes of operation)に幅広く対応しています。通常、MCryptなどのブロック暗号は、多くは64または128ビットの固定長データブロックを取り扱います。しかし、メッセージはどんな長さにもなりうることと、同じプレーンテキストを同じ鍵で暗号化すると常に同じ結果となることから、任意長のメッセージにもブロック暗号を適用できるようにする解決策がいくつか考え出されてきました。こうした解決策は、
ブロック暗号の利用モードとして知られています。MCryptがサポートするモードには、CBC、CFB、CTR、ECB、OFB、NCFBがあります。
MCryptの付随ライブラリにLibmcryptというものがあり、実際の暗号化関数はこちらのライブラリに含まれています。Windowsユーザーは
こちらで、Linuxユーザーは
こちらでそれぞれダウンロードできます。
著者注
PHP 5.0.0を使用している場合は、libmcrypt Version 2.5.6以上も必要となります。
Libmcryptのインストール手順は次のとおりです。
- libmcrypt.dllをダウンロードします。
- libmcrypt.dllファイルを、{php_home}/extと{Windows_home}/System32にコピーします。
- php.ini内のextension=php_mcrypt.dllという行のコメント記号(
;)を削除し、この行を有効にします。
- 更新したphp.iniファイルを保存します。
著者注
MCryptでは、CBC、OFB、CFB、ECBの4つの暗号モードを使用できます。libmcrypt-2.4.x以上を使用しているのであれば、MCrypt関数はnOFBおよびSTREAM暗号モードでも動作します。表2に、一般的に利用される暗号モードと、それぞれいつ使用するかを簡単に示します。
表2 MCryptのよく使用される暗号モード
| 暗号利用モード | 説明 |
| MCRYPT_MODE_ECB | ランダムデータに使用します。各種の鍵の暗号化に利用できます。 |
| MCRYPT_MODE_CBC | ファイル暗号化に使用します。 |
| MCRYPT_MODE_CFB | バイトストリームの暗号化に適しています。 |
| MCRYPT_MODE_OFB | 特に、エラー伝播が許されないアプリケーションで使用されます。 |
| MCRYPT_MODE_NOFB | OFBと同様ですが、より安全性が高いものです。 |
| MCRYPT_MODE_STREAM | WAKEやRC4などのストリームアルゴリズムが必要な場合に使用します。 |
表2の暗号モードに加え、MCryptでは現在次の暗号もサポートされています。
- MCRYPT_3DES
- MCRYPT_ARCFOUR
- MCRYPT_BLOWFISH
- MCRYPT_ENIGMA
- MCRYPT_GOST
- MCRYPT_IDEA(有償)
- MCRYPT_LOKI97
- MCRYPT_MARS
- MCRYPT_PANAMA
- MCRYPT_RIJNDAEL_128
著者注
MCryptの例
それでは、MCryptを使用してテキストファイルの内容を暗号化および復号化する方法を実際の例で見ていきましょう。先ほどの例と同じように、textfile.txtの内容を暗号化し、その結果をencrypted.txtファイルに格納します。ただし今回の例では、さらに続けてencrypted.txtファイルを復号し、暗号解除されたテキストをnewfile.txtファイルに格納します。
// Listing file_encrypt.php
<?php
$file = 'textfile.txt';
$initial_contents = file_get_contents($file);
if($initial_contents){
//This function opens the module of the algorithm and
//the mode to be used
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
//Create an initialization vector (IV) from a random source
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
//This function initializes all buffers needed for encryption
mcrypt_generic_init($td, $initial_contents, $iv);
//This function encrypts data
$encrypted_data = mcrypt_generic($td, $initial_contents);
$encrypted_file = @fopen('encrypted.txt','w');
$ok_encrypt = @fwrite($encrypted_file,$encrypted_data);
if($ok_encrypt){
echo 'The encrypted code was succesfully created '.
'in encrypted_file.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($encrypted_file);
mcrypt_generic_init($td, $initial_contents, $iv);
//This function decrypts data
$p_t = mdecrypt_generic($td, $encrypted_data);
$newfile = @fopen('newfile.txt','w');
$ok_decrypt = @fwrite($newfile,$p_t);
if($ok_decrypt){
echo 'The decrypted code was succesfully created '.
'in newfile.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($newfile);
//This function deinitializes an encryption module
mcrypt_generic_deinit($td);
//Close the mcrypt module
mcrypt_module_close($td);
}
?>
MHashによるハッシュの作成
MHashは
無料のライブラリで、開発者は数多くのハッシュアルゴリズムから使用するものを選べます。これらのアルゴリズムを使用して、チェックサムの計算、メッセージダイジェストの作成、他の署名の作成を行うことができます。
Libmhashのインストール
- libmhash.dllをダウンロードします。
- libmhash.dllファイルを{php_home}/extと{Windows_home}/System32にコピーします。
- php.ini内のextension=php_mhash.dllという行のコメント記号(;)を削除し、この行を有効にします。
- 更新したphp.iniファイルを保存します。
サポート対象ハッシュ
MHashが現在サポートするハッシュは次のとおりです。
- MHASH_ADLER32
- MHASH_CRC32
- MHASH_CRC32B
- MHASH_GOST
- MHASH_HAVAL128
- MHASH_HAVAL160
- MHASH_HAVAL192
- MHASH_HAVAL256
- MHASH_MD4
- MHASH_MD5
- MHASH_RIPEMD160
- MHASH_SHA1
- MHASH_SHA256
- MHASH_TIGER
- MHASH_TIGER128
- MHASH_TIGER160
次の例では、MHashを使ってtextfile.txtの暗号化を行い、その結果をencrypted.txtに書き込んでいます。
<?php
$file = 'textfile.txt';
$initial_contents = file_get_contents($file);
if($initial_contents){
//mhash() applies a hash function specified by MHASH_MD5
//to the $initial_contents
$encrypted = mhash(MHASH_MD5, $initial_contents);
// get current Unix timestamp
$current = time();
$salt = $current;
$password = "Octavia";
//mhash_keygen_s2k generates a key according to the hash function
//given and the password provided by the user.
$hash = mhash_keygen_s2k(MHASH_GOST, $password, $salt, 20);
//concatenate the $salt with the $hash
$key = $salt . "|" . bin2hex($hash);
$encrypted_file = @fopen('encrypted.txt','w');
$ok_encrypt = @fwrite($encrypted_file,
'mhash: '.bin2hex($encrypted).' mhash_keygen_s2k:
'.$key);
if($ok_encrypt){
echo 'The encrypted code was succesfully created '.
'in encrypted_file.txt !!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($encrypted_file);
}
?>
秘密鍵とCrypt_Blowfish
秘密鍵暗号方式では、1つの鍵を暗号化と復号の両方に使用します。このため「対称鍵」とも呼ばれます。例えば、一般的に使われるDESアルゴリズムは秘密鍵アルゴリズムです。Crypt_Blowfish PEARパッケージは、Blowfishブロック暗号に基づいており、秘密鍵あり/なしの双方向の暗号化に対応しています。Crypt_BlowfishパッケージはMCryptを必要としませんが、インストールされていれば使用できます。
最新のリリースバージョンは1.0.1(安定版)です。インストール方法は、他のPEARパッケージと同様です。
> pear install pear_package_name
このパッケージは、Blowfish.phpファイル内に定義されている2つのクラスを使用します。そのため、Crypt_Blowfishパッケージを使用するすべてのスクリプトでBlowfish.phpファイルをインクルードしておく必要があります。
require_once 'Crypt/Blowfish.php';
先ほどの例と同じ処理をする暗号化プログラムをCrypt_Blowfishで実装したコードを次に示します。
<?php
require_once 'Crypt/Blowfish.php';
$file = 'textfile.txt';
$initial_contents = file_get_contents($file);
if($initial_contents){
$bf = new Crypt_Blowfish('some secret key!');
// Encrypts a string
$encrypted = $bf->encrypt($initial_contents);
$encrypted_file = @fopen('encrypted.txt','w');
$ok_encrypt = @fwrite($encrypted_file,$encrypted);
if($ok_encrypt){
echo 'The encrypted code was succesfully created '.
'in encrypted_file.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($encrypted_file);
// Decrypts an encrypted string
$plaintext = $bf->decrypt($encrypted);
$newfile = @fopen('newfile.txt','w');
$ok_decrypt = @fwrite($newfile,$plaintext);
if($ok_decrypt){
echo 'The decrypted code was succesfully created '.
'in newfile.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($newfile);
}
?>
Crypt_RSAパッケージによる任意長の鍵を用いた暗号化
Crypt_RSAパッケージは双方向の暗号化をサポートし、RSAブロック暗号を基盤としています。Crypt_RSAでは任意長の鍵を使った暗号化と復号を行うことができます。
最新バージョンは1.0.0(安定版)です。インストール方法は、他のPEARパッケージと同様です。
> pear install pear_package_name
Crypt_RSAでは精緻な数値計算を行います。次のエクステンションのうち1つを使用します。
このパッケージを使用した例を次に示します。
<?php
require_once 'Crypt/RSA.php';
//Generates the pair keys
function generate_key_pair()
{
global $public_key,$private_key;
$key_pair = new Crypt_RSA_KeyPair(32);
//Returns public key from the pair
$public_key = $key_pair->getPublicKey();
//Returns private key from the pair
$private_key = $key_pair->getPrivateKey();
}
//Check runtime errors
function check_error(&$obj)
{
if ($obj->isError()){
$error = $obj->getLastError();
switch ($error->getCode()) {
case CRYPT_RSA_ERROR_WRONG_TAIL :
// nothing to do
break;
default:
// echo error message and exit
echo 'error: ', $error->getMessage();
exit;
}
}
}
$file = 'textfile.txt';
generate_key_pair();
$plain_text = file_get_contents($file);
//get string represenation of the public key
$key = Crypt_RSA_Key::fromString($public_key->toString());
$rsa_obj = new Crypt_RSA;
check_error($rsa_obj);
//Ecnrypts $plain_text by the key $key.
$encrypted = $rsa_obj->encrypt($plain_text, $key);
$encrypted_file = @fopen('encrypted.txt','w');
$ok_encrypt = fwrite($encrypted_file,$encrypted);
if($ok_encrypt){
echo 'The encrypted code was succesfully created '.
'in encrypted_file.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($encrypted_file);
$enc_text = $encrypted;
//Get string represenation of the private key
$key2 = Crypt_RSA_Key::fromString($private_key->toString());
check_error($key2);
//Check encrypting/decrypting function's behaviour
$rsa_obj->setParams(array('dec_key' => $key2));
check_error($rsa_obj);
//Decrypts $enc_text
$decrypted = $rsa_obj->decrypt($enc_text);
$newfile = @fopen('newfile.txt','w');
$ok_decrypt = @fwrite($newfile,$decrypted);
if($ok_decrypt){
echo 'The decrypted code was succesfully created '.
'in newfile.txt!!!'.'<br />';
}
else{
echo ("The write of this file failed!");
}
@fclose($newfile);
?>
Crypt_HMACでのハッシュ生成
Crypt_HMAC PEARパッケージには、
RFC 2104準拠のハッシュの算出に使用できるクラスが含まれています。Crypt_HMACの使い方は簡単で、秘密鍵と使用したいハッシュメソッド、そしてプレーンテキストの文字列を与えるだけです。Crypt_HMACではMD5とSHA-1アルゴリズムの両方をサポートしています。最新バージョンの安定版リリースは1.0.0です。インストール方法は他のPEARパッケージと同様です。
> pear install pear_package_name
Crypt_HMACを使用したハッシュ生成の簡単なサンプルを次に示します。
<?php
require_once 'Crypt/HMAC.php';
//Creating a key by repeating the "0x0b" character for 20 times
$key = str_repeat(chr(0x0b), 20);
//Creating an instance of the Crypt_HMAC class
//$crypt = new Crypt_HMAC($key, 'md5');
$crypt = new Crypt_HMAC($key, 'md5');
//Hashing function
echo $crypt->hash('Hello')."<br />";
$key = str_repeat(chr(0xaa), 10);
$data = str_repeat(chr(0xdd), 50);
//Sets key to use with hash
$crypt->setKey($key);
echo $crypt->hash($data)."¥";
?>
Crypt_DiffieHellmanパッケージでの秘密鍵の生成
このPEARパッケージは、Diffie-Hellman鍵交換暗号化プロトコルをPHP5用に実装したものです。このプロトコルを使用して外部の二者のために秘密鍵を生成すると、両者は生成された鍵を使って、安全性の低い経路であっても通信を行えるようになります。
最新リリースはバージョン0.2.1(ベータ版)で、他のPEARパッケージと同じ方法でインストールできます。
> pear install pear_package_name
次に示す2つのサンプルコードは、subject_1とsubject_2の二者間で秘密鍵を生成する2種類のアプリケーションです。最初のサンプルは、Diffie Hellmanアルゴリズムに基づくセキュアな秘密鍵を取得する最も簡単な方法を示しています。
<?php
//include Diffie Hellman functions
require_once 'Crypt/DiffieHellman.php';
//set the required options for two subjects
$subject_1 = array('prime'=>'123', 'generator'=>'7', 'private'=>'3');
$subject_2 = array('prime'=>'123', 'generator'=>'7', 'private'=>'34');
//apply Diffie Hellman algorithm
$subject_1_GK = new Crypt_DiffieHellman(
$subject_1['prime'], $subject_1['generator'],
$subject_1['private']);
$subject_2_GK = new Crypt_DiffieHellman(
$subject_2['prime'], $subject_2['generator'],
$subject_2['private']);
//generate keys
$subject_1_GK->generateKeys();
$subject_2_GK->generateKeys();
//compute the secret keys
$subject_1_SK = $subject_1_GK->computeSecretKey(
$subject_2_GK->getPublicKey())->getSharedSecretKey();
$subject_2_SK = $subject_2_GK->computeSecretKey(
$subject_1_GK->getPublicKey())->getSharedSecretKey();
//displaying the secret keys
echo('Subject_1_SK:'.$subject_1_SK.'<br />');
echo('Subject_2_SK:'.$subject_2_SK);
?>
2番目のサンプルでは、Diffie HellmanのBINARYモードを使用して秘密鍵を生成しています。
<?php
//include Diffie Hellman functions
require_once 'Crypt/DiffieHellman.php';
//set the required options for two subjects
$subject_1 = array('prime' =>
'9568094558049898340935098349053',
'generator'=>'2',
'private' => '2232370277237628823279273723742872289398723');
$subject_2 = array('prime' =>
'9568094558049898340935098349053',
'generator'=>'2',
'private' => '0389237288721323987429834389298232433363463');
//apply Diffie Hellman algorithm
$subject_1_GK = new Crypt_DiffieHellman(
$subject_1['prime'], $subject_1['generator'],
$subject_1['private']);
$subject_2_GK = new Crypt_DiffieHellman(
$subject_2['prime'], $subject_2['generator'],
$subject_2['private']);
//generate keys
$subject_1_GK->generateKeys();
$subject_2_GK->generateKeys();
//compute the secret keys using BINARY mode
$subject_1_SK = $subject_1_GK->computeSecretKey(
$subject_2_GK->getPublicKey(Crypt_DiffieHellman::BINARY),
Crypt_DiffieHellman::BINARY)->
getSharedSecretKey(Crypt_DiffieHellman::BINARY);
$subject_2_SK = $subject_2_GK->computeSecretKey(
$subject_1_GK->getPublicKey(Crypt_DiffieHellman::BINARY),
Crypt_DiffieHellman::BINARY)->
getSharedSecretKey(Crypt_DiffieHellman::BINARY);
//display the secret keys
echo('subject_1_SK:'.$subject_1_SK.'<br />');
echo('subject_2_SK:'.$subject_2_SK.'<br />');
?>
ここまで見てきた多様な暗号化オプションを幅広く用いれば、希望することはほとんどすべて実現できるはずです。暗号化は、セキュリティにおけるデリケートな問題です。そしてこの記事からも分かるように、ソリューションや実装は数多くあります。この記事の情報はあくまで入門レベルのことなので、そこから先は、実際のセキュアシステムで得た知識と経験に基づいて、セキュリティ、速度、実装時間の面で最適なバランスが取れた暗号化技術の実装を選ぶようにしてください。
経験豊富なPHP開発者。現在は、国内外のソフトウェア開発コンテストに参加するプログラミングチームの主任トレーナーを務める。国レベルの教育プロジェクト開発のコンサルティングも担当している。共著書に『XML technologies?XML in Java』があり、XML部分の執筆を担当。PHPやXMLのほか、ソフトウェアアーキテクチャ、Webサービス、UML、ハイパフォーマンスな単体テストについても関心を寄せている。