Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

PHPのHash/暗号化関数の使用方法まとめ

概要

「そもそもHashと暗号化って何が違うの?」この記事はそういった疑問を持っている私自身がまとめた記事でとても初歩的な内容になります。記事の紹介の中ではPHPをメインに話を進めます。PHPのHash関数、暗号化関数の種類が豊富で中々覚えづらい内容が多いです。今回の記事ではPHPによるHash/暗号化関数の使用とJAVAとの共通鍵暗号方式を意識した内容になります。また以前共通鍵暗号のAESについての内容をまとめたのでそちらも参照してください。AES暗号のまとめ - Yuta.Kikuchiの日記 はてなブックマーク - AES暗号のまとめ - Yuta.Kikuchiの日記

Hash、暗号化の違いについて

個人的な見識を書きますので間違っている可能性が高いです。

  • HashとはMD5やSHAなどのHashアルゴリズムに基づいて生成されるチェックサムです。特定の値に対してアルゴリズム(関数)をかけることによって1つの値を取得します。導き出されたHash値から元の値を出す事は難しいとされ、一般的には不可逆性を持つと言えます。Hashの目的としてはデータの送信、受け取り側の両方で共通のアルゴリズムでHashを確認するようなデータ改竄の防止などに利用されています。
  • 暗号化とはデータの秘匿性を守るための方法です。公開鍵暗号/共通鍵暗号などの方式がありますが、平文というデータを第三者に知られないように鍵を用いて暗号文を作成することが目的です。Hashと大きく異なるのが暗号文から鍵を利用して平文を取り出せるので、可逆性であると言えます。
  • 注意:Hashに対しても鍵を使用することがあります。たとえばSHA256のhmac方式等です。SHA256というアルゴリズムに対して鍵を指定してHashを作成します。

PHPでHash化

似たような関数が存在し分かりにくいので実際に使用した感じをまとめていきます。

登録されているHashアルゴリズム一覧
<?php
print_r(hash_algos());

Array
(
    [0] => md2
    [1] => md4
    [2] => md5
    [3] => sha1
    [4] => sha224
    [5] => sha256
    [6] => sha384
    [7] => sha512
    [8] => ripemd128
    [9] => ripemd160
    [10] => ripemd256
    [11] => ripemd320
    [12] => whirlpool
    [13] => tiger128,3
    [14] => tiger160,3
    [15] => tiger192,3
    [16] => tiger128,4
    [17] => tiger160,4
    [18] => tiger192,4
    [19] => snefru
    [20] => snefru256
    [21] => gost
    [22] => adler32
    [23] => crc32
    [24] => crc32b
    [25] => salsa10
    [26] => salsa20
    [27] => haval128,3
    [28] => haval160,3
    [29] => haval192,3
    [30] => haval224,3
    [31] => haval256,3
    [32] => haval128,4
    [33] => haval160,4
    [34] => haval192,4
    [35] => haval224,4
    [36] => haval256,4
    [37] => haval128,5
    [38] => haval160,5
    [39] => haval192,5
    [40] => haval224,5
    [41] => haval256,5
)
16ByteのMD5を得る方法

16進数表記であるため32文字でデータ量は16Byteになります。hash、hash_init/hash_update/hash_final、md5のどれでも同じ値を返します。

<?php
$data = 'yutakikuchi';

echo hash( 'md5', $data ) . "\n";
// cb9a2c9e74689deefb5bc42c0f33105c

$ctx = hash_init( 'md5' );
hash_update( $ctx, $data );
echo hash_final( $ctx ) . "\n";
// cb9a2c9e74689deefb5bc42c0f33105c

echo md5( $data ) . "\n";
// cb9a2c9e74689deefb5bc42c0f33105c

echo strlen( md5( $data ) ) . "\n"; 
// 32

バイナリ形式でデータを取得すると正確な16Byteになります。

<?php
$data = 'yutakikuchi';

echo hash( 'md5', $data, true ) . "\n";
// バイナリ文字列

$ctx = hash_init( 'md5' );
hash_update( $ctx, $data );
echo hash_final( $ctx, true ) . "\n";
// バイナリ文字列

echo md5( $data, true ) . "\n";
// バイナリ文字列

echo strlen( md5( $data, true ) ) . "\n"; 
// 16
32ByteのSHA256を得る方法

気をつけることはhashとhash_hmacは基本的に異なるHash値を生成する事です。hash_hmacは秘密鍵を必要とする関数のため使い方が変わります。hashとhash_init,hash_update,hash_finalの値は秘密鍵さえ使わなければ同じ値を返します。

<?php
$data = 'yutakikuchi';
echo hash( 'sha256', $data ) . "\n";
// a429281ab508d6fb7bc0cda4ce050cc3595bcf710f9dd2dc2b5b9b9a4b15346b

$ctx = hash_init( 'sha256' );
hash_update( $ctx, $data );
echo hash_final( $ctx ) . "\n";
// a429281ab508d6fb7bc0cda4ce050cc3595bcf710f9dd2dc2b5b9b9a4b15346b

echo hash_hmac( 'sha256', $data, '' ) . "\n";
// 854508cf63408f31441ae5f3c044d66196aa3308f49fa3ebc40ff093a0d95207

echo strlen( hash( 'sha256', $data ) ) . "\n";
// 64

MD5と同様にバイナリ形式の32Byteを取得することが出来ます。

<?php
$data = 'yutakikuchi';
echo hash( 'sha256', $data, true ) . "\n";
// バイナリ文字列

$ctx = hash_init( 'sha256' );
hash_update( $ctx, $data );
echo hash_final( $ctx, true ) . "\n";
// バイナリ文字列

echo hash_hmac( 'sha256', $data, '', true ) . "\n";
// バイナリ文字列

echo strlen( hash( 'sha256', $data, true ) ) . "\n";
// 32

PHPの暗号化

RIJNDAELの128bitブロック長/CBC

共通鍵のRIJNDAELの128bitブロック長、暗号化方式をCBCで暗号化を行います。InitialVectorと共通鍵を必要とします。以下は暗号文を送信する側のプログラムです。base64_encodeしていますが、mdJXSTJvugvrYpDSlpQcQEbIbjFVyGiVtiu/rZYz28s=とInitialVectorのbj4OWQybnPsIVKBEWcK75A==という値を受信側に送信します。

<?php
//暗号化方式をRIJNDAELの128bitブロック長、暗号化方式をCBC
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);  

//Initial Vectorの大きさ
echo "iv size:" . $iv_size . "\n";
// iv size:16

//Initial Vector生成
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
echo "iv value:" . base64_encode( $iv ) . "\n";
// iv value:bj4OWQybnPsIVKBEWcK75A==

//共通鍵
$key = "secret key!!!!!!!";

//暗号化したい平文
$text = "This is plain text";
echo "PlainText Value:" . $text . "\n";
// PlainText Value:This is plain text

//暗号化方式をRIJNDAELの128bitブロック長、暗号化方式をCBC
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
echo "Crypt Value:" . base64_encode( $crypttext ) . "\n";
// Crypt Value:mdJXSTJvugvrYpDSlpQcQEbIbjFVyGiVtiu/rZYz28s=

暗号文とInitialVectorを取得した受信側は以下のコードで復号化を行います。

<?php
$crypttext = base64_decode( 'mdJXSTJvugvrYpDSlpQcQEbIbjFVyGiVtiu/rZYz28s=' );
$iv = base64_decode( 'bj4OWQybnPsIVKBEWcK75A==' );

$key = 'secret key!!!!!!!';

//復号化
$decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $crypttext, MCRYPT_MODE_CBC, $iv);
echo "DeCrypt Value:" . $decrypttext . "\n";
//DeCrypt Value:This is plain text
JAVAでのPadding

mcrypt_encrypt関数では平文データの大きさが n * blocksize でない場合'\0'パディングを行います。keyに対しても同様なようです。JAVAとの送受信を行う時にはJAVA側が'\0'パディングに対応していないためPHP側でpkcs5パディングに変換してやる必要があります。PHPでやる場合はmcrypt_encrypt関数に平文データを入れる前にn * blocksize長になるようにpkcs5パディングを行います。ここではpkcs5のパティングと逆パディングの関数例を記述して上の例をやってみます。PHPでのpkcs5paddingについては以下のページに関数例が載っています。PHP: Mcrypt 関数 - Manual はてなブックマーク - PHP: Mcrypt 関数 - Manual

<?php

//暗号化方式をRIJNDAELの128bitブロック長、暗号化方式をCBC
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);  

//Initial Vectorの大きさ
echo "iv size:" . $iv_size . "\n";

//Initial Vector生成
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
echo "iv value:" . base64_encode( $iv ) . "\n";
// 17E9IF8sHSy/ik9IsSvJCg==

//共通鍵
$key = "secret key!!!!!!!";

//暗号化したい平文
$text = "This is plain text";
echo "PlainText Value:" . $text . "\n";

// pkcs5padding
$blocksize = 16;
$pad = $blocksize - (strlen($text) % $blocksize);
$text = $text . str_repeat(chr($pad), $pad); 
echo "PlainText Length:" . strlen( $text ) . "\n";
// 32

//暗号化方式をRIJNDAELの128bitブロック長、暗号化方式をCBC
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
echo "Crypt Length:" . strlen( $crypttext ) . "\n";
// 32
echo "Crypt Value:" . base64_encode( $crypttext ) . "\n";
//9y/kVl0iLHYxJUsWnHQdvTqY0VM8OIzhELnAJqDgpj0=

受信側では逆パディングを行います。

<?php
$crypttext = base64_decode( '9y/kVl0iLHYxJUsWnHQdvTqY0VM8OIzhELnAJqDgpj0=' );
$iv = base64_decode( '17E9IF8sHSy/ik9IsSvJCg==' );

$key = 'secret key!!!!!!!';

//復号化
$decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $crypttext, MCRYPT_MODE_CBC, $iv);
//unpadding
$pad = ord($decrypttext{strlen($decrypttext)-1}); 
$data = substr($decrypttext, 0, -1 * $pad); 
echo "DeCrypt Value:" . $data . "\n";
// DeCrypt Value:This is plain text