
概要
Redhat系のOSを使っている方は、そろそろPHPのバージョンが5系からアップデートする時期が迫っていると思います。 PHPの5.Xから7.2以上に上げるときに困るのが Mcrypt関数 の廃止です。
移行先として推奨されているOpenSSLへの単純な移行だと復号が出来なくなってしまいます。 そこで Mcrypt から OpenSSL に代替した場合でも、復号可能なコードの実装例を紹介します。
参考
移行の結果
既存のコード
暗号化
function reversible_encrypt($key, $iv, $data)
{
 $base64_data = base64_encode($data);
 $resource = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
 mcrypt_generic_init($resource, $key, $iv);
 $encrypted_data = mcrypt_generic($resource, $base64_data);
 mcrypt_generic_deinit($resource);
 //後始末
 mcrypt_module_close($resource);
 $encrypted_data_base64 = base64_encode($encrypted_data);
 return $encrypted_data_base64;
}
複合化
function reversible_decrypt($key, $iv, $encrypted_data_base64)
{
 $encrypted_data = base64_decode($encrypted_data_base64);
 $resource = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
 mcrypt_generic_init($resource, $key, $iv);
 $base64_decrypted_data = mdecrypt_generic($resource, $encrypted_data);
 mcrypt_generic_deinit($resource);
 $decrypted_data = base64_decode($base64_decrypted_data);
 //後始末
 mcrypt_module_close($resource);
 return $decrypted_data;
}
修正コード
共通処理
/**
 * 暗号化キーの長さが足りない場合、16byte以上の鍵長まで拡張する
 *
 * @see https://github.com/LancersDevTeam/PHP_versionup/blob/master/PHP5.6toPHP7.3/1.1_mcrypt%E5%AF%BE%E5%BF%9C.md
 *
 * @param string $key
 *
 * @return string
 *
 * @throws LengthException
 */
function extension16BytesOrMoreKey(string $key): string
{
 $length = strlen($key);
 if ($length === 0) throw new LengthException('暗号化キーが空です');
 if ($length < 16) $key = str_repeat($key, ceil(16 / $length));
 return $key;
}

/**
 * 0x00パディングを行う
 *
 * @see https://github.com/LancersDevTeam/PHP_versionup/blob/master/PHP5.6toPHP7.3/1.1_mcrypt%E5%AF%BE%E5%BF%9C.md
 *
 * @param string $data
 *
 * @return string
 */
function paddingZero(string $data): string
{
 $pad_length = strlen($data) % 8;
 if ($pad_length !== 0) $data .= str_repeat("\x00", 8 - $pad_length);
 return $data;
}
暗号化
function reversible_encrypt(string $key, string $iv, string $data): string
{
 $base64_data = base64_encode($data);
 return openssl_encrypt(paddingZero($base64_data), 'BF-CBC', extension16BytesOrMoreKey($key), OPENSSL_ZERO_PADDING, $iv);
}
復号化
function reversible_decrypt(string $key, string $iv, ?string $encrypted_data_base64): string
{
 $base64_decrypted_data = openssl_decrypt($encrypted_data_base64, 'BF-CBC', extension16BytesOrMoreKey($key), OPENSSL_ZERO_PADDING, $iv);
 return base64_decode($base64_decrypted_data);
}
BlowfishにおけるMcryptとOpenSSLの違い
動作を確認したところ、以下のようになっていました。
暗号鍵が短い場合の処理の違い
暗号化キーが abc
だとします。
Mcrypt
16bit以上の長さになるまで循環されて拡張される。
abcabcabcabcabcabc
OpenSSL
16bitの長さになるまで0で埋めて拡張される。
abc\0\0\0\0\0\0\0\0\0\0\0\0\0
対策
OpenSSLの関数を呼び出す前に、こちらで暗号鍵を長くします。
function extension16BytesOrMoreKey(string $key): string
{
 $length = strlen($key);
 if ($length === 0) throw new LengthException('暗号化キーが空です');
 if ($length < 16) $key = str_repeat($key, ceil(16 / $length));
 return $key;
}
暗号化されたデータの違い
暗号化する際の文字列を固定ブロック長に分割したときの動作が違うようでした。
暗号化キーが abc
だとします。
Mcrypt
固定ブロック長(1octet)のサイズになるように、NULL(ゼロ)パディングされます。
abc\x00\x00\x00\x00\x00
OpenSSL
PKCS#7パディングが行われます。
abc\x05\x05\x05\x05\x05
\x05
になります。
対策
暗号化する前に、暗号化キーに対して固定ブロック長のNULL(ゼロ)パディングを自分で実装して適用します。
function paddingZero(string $data): string
{
 $pad_length = strlen($data) % 8;
 if ($pad_length !== 0) $data .= str_repeat("\x00", 8 - $pad_length);
 return $data;
}
最後に
Mcrypt は既に廃止されたライブラリということもあり、既に他の言語でも OpenSSL が使われています。 今回のように Mcrypt でした暗号が全く同じ方式だったとしても、ちょっとした仕様の違いで OpenSSL で復号が出来ない場合があります。
Mcrypt で暗号化したデータを復号しなければならない案件の都合上、 Mcrypt を使い続けるか書き直すかのどちらかでしたが、今回は Mcrypt で暗号している部分が少なかった事もあり OpenSSL で書き直しました。
参考サイト様は大変参考になりました。ありがとうございました。