JavaとPHPで暗号化したり復号化したりで相互運用(AES/ECB)

2018年5月7日

通信経路上の安全を確保する必要があって、ローカルで文字列を暗号化、
サーバーに転送し復号したりなんかする必要があったのですが、
最近はAESとかいうのだそうでちょっとやってみました。

アルゴリズムを常に固定にする方式をECB、毎回変える方式をCBCと呼ぶそうですが、
ECBの場合は同じものを暗号化すると毎回同じ形になるので、
仮に盗まれた場合、そのまま使われる危険性があることに注意します。

Javaで暗号化/復号化してBase64の文字列にするコード

String secret_key = “0123456789012345";  // <-128bitのキー
keyBytes = key.getBytes(); // バイト列に変換
SecretKeySpec sKey = new SecretKeySpec(secret_key, “AES");
try {
Cipher cipher1 = Cipher.getInstance(“AES/ECB/PKCS5Padding");
cipher1.init(Cipher.ENCRYPT_MODE, sKey);
String enc = new BASE64Encoder().encode( cipher1.doFinal(data) ); // Base64の文字列に

Cipher cipher2 = Cipher.getInstance(“AES/ECB/PKCS5Padding");
cipher2.init(Cipher.DECRYPT_MODE, sKey);
String dec = new BASE64Encoder().encode( cipher2.doFinal(data) ); // Base64の文字列に

} catch (throwable t) {}

しかしこれで作った文字列を
PHPで暗号/復号すると一致しないんです。
なんでかというと、パディング(padding)の問題らしく。

AESは16バイトずつ暗号化するのですが、暗号化する際に文字が16バイトに満たない時、
上記の場合16バイト単位の固定長までPKCS#5Paddingというアルゴリズムで埋めます。
しかし、PHP(mcrypt_decrypt)はゼロパディング(\0で埋める)ので、
mcrypt_decryptだけだとパディング部分がうまく一致しません。

(その違い例 あくまで概念)
Java: 「暗号化してよ。あぁ」→「暗号化してよ。あぁPKCS5#Pa」→暗号化
PHP:  「暗号化してよ。あぁ」→「暗号化してよ。あぁ00000000」→暗号化

それならJava側で「ゼロパディング」方式にすればいいやと思ったら、
 「Java 暗号化拡張機能リファレンスガイド」の付録 A (Cipherクラス)によると、
Javaでなんかそういう単純に処理しちゃうのは無いみたいなんですよ!

「ゼロでパディング機能なんて、お前らgetByte()して固定長にするのくらい簡単だろう?」
とでも言いたいようです。それが冗長で嫌なんですけど・・・

NoPadding(パディング無し)とすると、元ネタの文字列がnバイトに満たない長さの時に
エラーになってしまいますので、結局は常にパディングする必要があります。

結論:
PHP側でPKCS5パディング(PKCS5Padding)することにしました。
だって、まんまサンプルコードがあったから。

暗号するときは、PKCS5パディングして、暗号化、Base64エンコード。
復号するときは、Base64デコード、復号化、PKCS5パディング解除。

ああぁ ややこしい。

PHPで暗号化/復号化してBase64の文字列にするコード

function decode($text) {
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$bin = pack('H*’, bin2hex( base64_decode($text)) );
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $bin, MCRYPT_MODE_ECB, $iv);
return rtrim( pkcs5_unpad($decrypted) );
}

function encode($text) {
$key = '0123456789012345’;
$text=pkcs5_pad($text,16);  // AESは16バイトずつ暗号化
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$bin = pack('H*’, bin2hex($text) );
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $bin, MCRYPT_MODE_ECB, $iv);
return base64_encode($encrypted);
}

function pkcs5_pad($text, $blocksize) {
$pad = $blocksize – (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}

function pkcs5_unpad($text) {
$pad = ord($text{strlen($text)-1});
if ($pad > strlen($text)) return false;
if (strspn($text, chr($pad), strlen($text) – $pad) != $pad) return false;
return substr($text, 0, -1 * $pad);
}