2009年2月16日月曜日

PHPで AES方式 (Rijndael-128)で暗号化するメモ

(2012/12/01追記)
手っ取り早くPHPで暗号化したいなら、CodeBook.phpを使うのがお勧め。
(追記終わり)


AESとは?
暗号化方式の1つ。ブロック長は128bitsのみ、鍵長は128・192・256bitsの3つから選択できる。
AESの元となる暗号化方式はRijndaelだが、Rijndaelではブロック長についても128・192・256bitsから選択できるという違いがある。


PHPでの使用例(CBCの場合)
$key = '秘密の合言葉';
$text = '暗号化するメッセージ';

srand();

$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);

$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
$hex = bin2hex($encrypted);

$bin = pack('H*', $hex);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $bin, MCRYPT_MODE_CBC, $iv);

echo $text . ' => ' . rtrim($decrypted);
  • AESのブロック長は128bits = 16Bytesなので、サイズは16決めうちでも良いかも。
  • CBCの初期化ベクトル(IV)は毎回違うものを生成しないといけない(参考:ブロック暗号モード(block cipher mode))ので、暗号化したデータと一緒にIVの値も渡さないと復号できない。
  • IVは第3者に見られても問題ない。IVが分かってもKEYが分からなければ復号は不可能だし、毎回違うIVを生成する限り(ECBの場合のように)データの一部を推測することも不可能。(ただしデータの最初のブロックの中身を知っている場合は、その部分を変更できるようだ。参考:PHPのmcrypt関数で使用する初期化ベクトル(IV)とは公開されてはまずいものなのでしょうか? (中略)人力検索はてな
  • 暗号化したデータ(およびIV)はそのままではバイナリなので、http等で渡すにはhexにしてから渡す。(参考:[PHP-users 13878] Re: hex2bin??PHP: pack - Manual
  • 復号した文字列は、文字列長が足りない分に\0が入っているのでrtrim()で削除。ただし元々のデータに\0が入りうる場合はそれなりの対処が必要。(参考:「PHPで暗号化・復号あれこれ」の続き - Do You PHP はてな
  • Java等、\0以外でパディングするプログラムと連携する場合はパディング方法が同じになるよう注意すること。
  • MCRYPT_RANDを使う場合は事前にsrand()する必要がある。
    注意: MCRYPT_RAND を使用する場合、乱数生成器を初期化するために、 必ず mcrypt_create_iv() の前に srand() をコールしてください。rand() のように、自動的に 初期化されるわけではありません。


モードがECBの場合
$key = '秘密の合言葉';
$text = '暗号化するメッセージ';

srand();
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);

$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_ECB, $iv);
$hex = bin2hex($encrypted);

//暗号化に使ったのと別のIVを使ってみる
$iv2 = mcrypt_create_iv($size, MCRYPT_RAND);
$bin = pack('H*', $hex);

$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $bin, MCRYPT_MODE_ECB, $iv2);

echo bin2hex($iv) . ' : ' . bin2hex($iv2) . '<br />';
echo $text . ' => ' . rtrim($decrypted);
  • ECBの場合、実際にはIVは使っていないので、別のIVでも復号できる。
  • でもmcrypt_encrypt()にIVを渡さないとWarningが出る。(Warningが出ても、暗号化・復号はできる。)
    Attempt to use an empty IV, which is NOT recommend


鍵長についてPHPのラインダールでは鍵長は128・192・256bitsの3つ全てをサポートしている。
print_r(mcrypt_module_get_supported_key_sizes(MCRYPT_RIJNDAEL_128));

array(3) {
 [0]=>int(16)
 [1]=>int(24)
 [2]=>int(32)
}

鍵の長さが鍵長より短い場合、NULL文字(\0)によりパディングされる。
PHPのラインダールでは鍵長を指定するパラメータが無いので、鍵長は渡した鍵の長さに応じて決まる。
実験したところ、渡した鍵が収まるうちで最も小さい鍵長が使われることが分かった。
(実験用のfunction)
//同じKEYか確かめるfunction
function is_same_key($key1, $key2) {
 $text = '暗号化するメッセージ';
 srand();
 $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
 $iv = mcrypt_create_iv($size, MCRYPT_RAND);
 $encrypted1 = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key1, $text, MCRYPT_MODE_CBC, $iv);
 $encrypted2 = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key2, $text, MCRYPT_MODE_CBC, $iv);
 return ($encrypted1 === $encrypted2);
}
//足りない分をNULL文字で埋めるfunction
//(str_pad()では\0によるパディングが上手くいかなかった)
function pad($str, $len) {
 for ($i = strlen($str); $i < $len; $i++) {
  $str .= "\0";
 }
 return $str;
}
  • 鍵の文字列が15Bytesの場合、鍵長は16Bytes
    $key = str_repeat('a', 15);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //false
    var_dump(is_same_key($key, pad($key, 32))); //false
  • 鍵の文字列が16Bytesの場合、鍵長は16Bytes
    $key = str_repeat('a', 16);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //false
    var_dump(is_same_key($key, pad($key, 32))); //false
  • 鍵の文字列が17Bytesの場合、鍵長は24Bytes
    $key = str_repeat('a', 17);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //true
    var_dump(is_same_key($key, pad($key, 32))); //false
  • 鍵の文字列が24Bytesの場合、鍵長は24Bytes
    $key = str_repeat('a', 24);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //true
    var_dump(is_same_key($key, pad($key, 32))); //false
  • 鍵の文字列が25Bytesの場合、鍵長は32Bytes
    $key = str_repeat('a', 25);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //true
    var_dump(is_same_key($key, pad($key, 32))); //true
  • 鍵の文字列が32Bytesの場合、鍵長は32Bytes
    $key = str_repeat('a', 25);
    var_dump(is_same_key($key, pad($key, 16))); //true
    var_dump(is_same_key($key, pad($key, 24))); //true
    var_dump(is_same_key($key, pad($key, 32))); //true


Windowsの場合のmcryptのセットアップ方法
php.iniでmcryptモジュールを有効にすれば使えた。
extension=php_mcrypt.dll
mcrypt_create_iv()の第2引数には注意。
Windows でサポートされているのは MCRYPT_RAND のみです。なぜなら、Windows には(当然) /dev/random あるいは /dev/urandom が存在しないからです。


Linux(CentOS)の場合のmcryptのセットアップ方法

yumでインストールする。(参考:floatingdays: PHPに後からmb_stringを追加する方法
yum install php-mcrypt
通常はこれでOK。
自分の環境の場合、インストールされたphp-mcryptはバージョンは5.1.6で、PHPのバージョンがなぜか5.2.5だったのでmcryptが有効にならなかった。(参考:使えるねっと :: トピックを表示 - mcryptのインストールについて
どうやらこのPHPはutterramblingsからインストールしたようだ。なのでphp-mcryptもutterramblingsからインストール。これで無事に mcrypt_list_algorithms() できた。



参考(AESについて):
 AES暗号 - Wikipedia(RijndaelのBit長については他のサイトの記述と違う?)
 The AES-CBC Cipher Algorithm and Its Use with IPsec
 ブロック暗号(block cipher)

参考:(PHPで暗号化)
 PHP: mcrypt_encrypt - Manual
 ウノウラボ Unoh Labs: PHPで暗号化・復号あれこれ
 mcryptの動作がマニュアルと違うように見える件 - yandodの日記

参考(Javaの場合):
 Java「AES暗号」メモ(Hishidama's AES Sample)
 Cipher (Java Platform SE 6)
 PHPとJAVAの暗号・復号の連携-教えてR2! ~ the last summer of C-3PO ~
 簡単な暗号化 - Java編

参考(Perlの場合):
 Crypt::CBC - 暗号ブロック連鎖(Cipher Block Chaining)モードでデータを暗号化します

参考(PKCS#5のパディングについて):
 68user's page 掲示板: 過去ログ
 PHP: Mcrypt Functions - Manual(pkcs5_pad() / pkcs5_unpad())
 StackTrace Blog - Blowfishによる暗号化・復号をJava→PHPで行う

その他の参考:
 ASCIIコードによる文字表現(NULL文字等について)

0 件のコメント:

コメントを投稿