昨日(PHPで暗号化された情報をJavaで復号する - S_a_k_Uの日記みたいなDB)の続き。
初見で処理が判りにくかったので、シンプルなコードにしてみた。
それからストレッチング処理にも対応させてみた。こちらは変数名やコメントなんかも見直して、PHPとJavaでソースを極力合せてみた。
#少しPHP力が上がった気がするw
シンプルバージョン
PHP(暗号化と復号処理)
<?php // 暗号化する文字列 $data = 'cleattext'; // 共通のパスワード $pass = 'mypass'; $encrypted = encrypt($data, $pass); echo 'Encrypted :'.$encrypted.PHP_EOL; echo 'Decrypted :'.decrypt($encrypted, $pass).PHP_EOL; /** * 復号処理 * * @param data $edata 暗号化された文字列 * @param string $password 共通のパスワード * @return decrypted data 復号された文字列 */ function decrypt($edata, $password) { // BASE64からデコードする $data = base64_decode($edata); // SALTを抽出する(1~16バイト目) $salt = substr($data, 0, 16); // 暗号化された文字列を取得する(17バイト目以降) $ct = substr($data, 16); // 共通のパスワードとSALTを結合する $data00 = $password.$salt; // 暗号化キー=共通のパスワード+SALTのハッシュ値 $key = hash('sha256', $data00, true); // 初期ベクター=暗号化キー+共通のパスワード+SALTのハッシュ値の先頭16バイト $iv = substr(hash('sha256', $key.$data00, true), 0, 16); echo '[復号]暗号化キー :'.bin2hex($key).PHP_EOL; echo '[復号]初期化ベクター:'.bin2hex($iv).PHP_EOL; // 暗号化された文字列を暗号化キーと初期化ベクターで復号する return openssl_decrypt($ct, 'AES-256-CBC', $key, true, $iv); } /** * 暗号化処理 * * @param data $data 暗号化する文字列 * @param string $password 共通のパスワード * @return base64 encrypted data 暗号化された文字列 */ function encrypt($data, $password) { // SALTをランダム文字列で生成する $salt = openssl_random_pseudo_bytes(16); // 暗号化キー=共通のパスワード+SALTのハッシュ値 $key = hash('sha256', $password.$salt, true); // 初期ベクター=暗号化キー+共通のパスワード+SALTのハッシュ値の先頭16バイト $iv = substr(hash('sha256', $key.$password.$salt, true), 0,16); echo '[暗号化]暗号化キー :'.bin2hex($key).PHP_EOL; echo '[暗号化]初期化ベクター:'.bin2hex($iv).PHP_EOL; // 暗号化する文字列を暗号化キーと初期化ベクターで暗号化する $encrypted_data = openssl_encrypt($data, 'AES-256-CBC', $key, true, $iv); // BASE64へエンコードする return base64_encode($salt.$encrypted_data); } ?>
Java(復号処理のみ)
import java.security.MessageDigest; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.ArrayUtils; public class CryptTestSimple { // 共通のパスワード private static String PASS = "mypass"; // SHA256ハッシュ値取得アルゴリズムの名前 private static String HASH_ALGO_NAME = "SHA-256"; // 復号化アルゴリズムの名前 private static String CRYPT_ALGO_NAME = "AES/CBC/PKCS5Padding"; public static void main(String[] args) { try { String encrypted = "BvQnds0k+lmi0Bd6D/FTGbpsx4s52qhdNcsK87D5aGA="; System.out.println("Encrypted :" + encrypted); String decStr = new CryptTestSimple().decrypt(encrypted); System.out.println("Decrypted :" + decStr); } catch (Exception e) { e.printStackTrace(); } } /** * 復号処理 * @param src 復号する文字列 * @return 復号した文字列 * @throws Exception */ public String decrypt(String src) throws Exception { // SHA256のハッシュ値変換アルゴリズム MessageDigest md = MessageDigest.getInstance(HASH_ALGO_NAME); // BASE64からデコードする byte[] data = Base64.decodeBase64(src.getBytes()); // SALTを抽出する(1~16バイト目) byte[] salt = Arrays.copyOfRange(data, 0, 16); // 暗号化された文字列を取得する(17バイト目以降) byte[] ct = Arrays.copyOfRange(data, 16, data.length); // 共通のパスワードとSALTを結合する byte[] data00 = ArrayUtils.addAll(PASS.getBytes(), salt); // 暗号化キー=共通のパスワード+SALTのハッシュ値 md.update(data00); byte[] key = md.digest(); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); // 初期ベクター=暗号化キー+共通のパスワード+SALTのハッシュ値の先頭16バイト md.update(ArrayUtils.addAll(key, data00)); byte[] iv = Arrays.copyOfRange(md.digest(), 0, 16); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); System.out.println("[復号]暗号化キー :" + new String(Hex.encodeHex(key))); System.out.println("[復号]初期化ベクター:" + new String(Hex.encodeHex(iv))); // 暗号化された文字列を暗号化キーと初期化ベクターで復号する Cipher cipher = Cipher.getInstance(CRYPT_ALGO_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); return new String(cipher.doFinal(ct)); } }
実行結果
PHP(暗号化→復号)
[暗号化]暗号化キー :68ec7052dd283574c03c71a6514041c3ede2b00c53a812e6c4d6e512423d0326 [暗号化]初期化ベクター:e0171a8b73c4a4903334c915e85a77a9 Encrypted :Qz+JY0f1lyXCNyZyPq8vYaxFyMoBOAW3baKUjbzMt+o= [復号]暗号化キー :68ec7052dd283574c03c71a6514041c3ede2b00c53a812e6c4d6e512423d0326 [復号]初期化ベクター:e0171a8b73c4a4903334c915e85a77a9 Decrypted :cleattext
Java(復号のみ)
Encrypted :Qz+JY0f1lyXCNyZyPq8vYaxFyMoBOAW3baKUjbzMt+o= [復号]暗号化キー :68ec7052dd283574c03c71a6514041c3ede2b00c53a812e6c4d6e512423d0326 [復号]初期化ベクター:e0171a8b73c4a4903334c915e85a77a9 Decrypted :cleattext
ストレッチングバージョン
PHP(暗号化と復号処理)
<?php // SHA256ハッシュ値取得アルゴリズムのストレッチング回数 $hash_algo_loop = 100; // 暗号化する文字列 $data = 'cleattext'; // 共通のパスワード $pass = 'mypass'; $encrypted = encrypt($data, $pass, $hash_algo_loop); echo 'Encrypted :'.$encrypted.PHP_EOL; echo 'Decrypted :'.decrypt($encrypted, $pass, $hash_algo_loop).PHP_EOL; /** * 復号処理 * * @param string $src 暗号化された文字列(BASE64) * @param string $password 共通のパスワード * @param int $hash_algo_loop ストレッチング処理の回数 * @return string 復号された文字列 */ function decrypt($src, $password, $hash_algo_loop) { // BASE64からデコードする $data = base64_decode($src); // SALTを抽出する(1~16バイト目) $salt = substr($data, 0, 16); // 暗号化された文字列を取得する(17バイト目以降) $ct = substr($data, 16); // 共通のパスワードにSALTを結合する $data00 = $password.$salt; // ストレッチング処理(共通のパスワード+SALT) $hash = stretch($data00, $hash_algo_loop); // 暗号化キーはハッシュ値の1~32バイト(32バイト) $key = substr($hash, 0, 32); // 初期ベクターはハッシュ値の33~48バイト目(16バイト) $iv = substr($hash, 32, 48); echo '[復号]暗号化キー :'.bin2hex($key).PHP_EOL; echo '[復号]初期化ベクター:'.bin2hex($iv).PHP_EOL; // 暗号化された文字列を暗号化キーと初期化ベクターで復号する return openssl_decrypt($ct, 'AES-256-CBC', $key, true, $iv); } /** * 暗号化処理 * * @param string $src 暗号化する文字列 * @param string $password 共通のパスワード * @param int $hash_algo_loop ストレッチング処理の回数 * @return string 暗号化された文字列(BASE64) */ function encrypt($src, $password, $hash_algo_loop) { // SALTをランダム文字列で生成する $salt = openssl_random_pseudo_bytes(16); // 共通のパスワードにSALTを結合する $data00 = $password.$salt; // ストレッチング処理(共通のパスワード+SALT) $hash = stretch($data00, $hash_algo_loop); // 暗号化キーはハッシュ値の1~32バイト(32バイト) $key = substr($hash, 0, 32); // 初期ベクターはハッシュ値の33~48バイト目(16バイト) $iv = substr($hash, 32, 48); echo '[暗号化]暗号化キー :'.bin2hex($key).PHP_EOL; echo '[暗号化]初期化ベクター:'.bin2hex($iv).PHP_EOL; // 暗号化する文字列を暗号化キーと初期化ベクターで暗号化する $encrypted_data = openssl_encrypt($src, 'AES-256-CBC', $key, true, $iv); // BASE64へエンコードする return base64_encode($salt.$encrypted_data); } /** * ハッシュ値のストレッチング処理 * * @param string $src ストレッチ処理する値 * @param int $hash_algo_loop ストレッチング処理の回数 * @return string ハッシュ値(暗号化キー32バイト+初期ベクター16バイト) */ function stretch($src, $hash_algo_loop) { // ストレッチング処理 $last_hash = ''; $key = ''; $iv = ''; for ( $i = 0 ; $i < $hash_algo_loop ; $i++ ) { // 暗号化キー=ループ内1回目のハッシュ値 $key = hash('sha256', $last_hash.$src, true); // 初期ベクター=ループ内2回目のハッシュ値 $iv = hash('sha256', $key.$src, true); // 次のストレッチ用のハッシュ値 $last_hash = $iv; } // 暗号化キー32バイト+初期ベクターはハッシュ値の先頭16バイトを返す return $key.substr($iv, 0, 16); } ?>
Java(復号処理のみ)
import java.security.MessageDigest; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.ArrayUtils; public class CryptTestStretching { // 共通のパスワード private static String PASS = "mypass"; // SHA256ハッシュ値取得アルゴリズムの名前 private static String HASH_ALGO_NAME = "SHA-256"; // SHA256ハッシュ値取得アルゴリズムのストレッチング回数 private static int HASH_ALGO_LOOP = 100; // 復号化アルゴリズムの名前 private static String CRYPT_ALGO_NAME = "AES/CBC/PKCS5Padding"; public static void main(String[] args) { try { String encrypted = "ebg9wjZKaZ/zKbmY/r2eHiuUgO3Q6Mlz7SacAC5Lhno="; System.out.println("Encrypted :" + encrypted); String decStr = new CryptTestStretching().decrypt(encrypted); System.out.println("Decrypted :" + decStr); } catch (Exception e) { e.printStackTrace(); } } /** * 復号処理 * @param src 復号する文字列 * @return 復号した文字列 * @throws Exception */ public String decrypt(String src) throws Exception { // BASE64からデコードする byte[] data = Base64.decodeBase64(src.getBytes()); // SALTを抽出する(1~16バイト目) byte[] salt = Arrays.copyOfRange(data, 0, 16); // 暗号化された文字列を取得する(17バイト目以降) byte[] ct = Arrays.copyOfRange(data, 16, data.length); // 共通のパスワードとSALTを結合する byte[] data00 = ArrayUtils.addAll(PASS.getBytes(), salt); // ストレッチング処理(共通のパスワード+SALT) byte[] hash = stretch(data00); // 暗号化キーはハッシュ値の1~32バイト(32バイト) byte[] key = Arrays.copyOfRange(hash, 0, 32); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); // 初期ベクターはハッシュ値の33~48バイト目(16バイト) byte[] iv = Arrays.copyOfRange(hash, 32, 48); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); // 暗号化キーと初期化ベクターの確認 System.out.println("[復号]暗号化キー :" + new String(Hex.encodeHex(key))); System.out.println("[復号]初期化ベクター:" + new String(Hex.encodeHex(iv))); // 暗号化された文字列を暗号化キーと初期化ベクターで復号する Cipher cipher = Cipher.getInstance(CRYPT_ALGO_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); return new String(cipher.doFinal(ct)); } /** * ハッシュ値のストレッチング処理 * @param src ストレッチ処理する値 * @return ハッシュ値(暗号化キー32バイト+初期ベクター16バイト) * @throws Exception */ private byte[] stretch(byte[] src) throws Exception { // SHA256のハッシュ値変換アルゴリズム MessageDigest md = MessageDigest.getInstance(HASH_ALGO_NAME); // ストレッチング処理 byte[] lastHash = {}; byte[] key = {}; byte[] iv = {}; for ( int i = 0 ; i < HASH_ALGO_LOOP ; i++ ) { // 暗号化キー=ループ内1回目のハッシュ値 md.update(ArrayUtils.addAll(lastHash, src)); key = md.digest(); // 初期ベクター=ループ内2回目のハッシュ値 md.update(ArrayUtils.addAll(key, src)); iv = md.digest(); // 次のストレッチ用のハッシュ値 lastHash = iv; } // 暗号化キー32バイト+初期ベクターはハッシュ値の先頭16バイトを返す return ArrayUtils.addAll(key, Arrays.copyOfRange(iv, 0, 16)); } }
実行結果
PHP(暗号化→復号)
[暗号化]暗号化キー :71355b76c028d8a3816d6e05ab6d6669e70579944ef031fe490dd667fa49bcd1 [暗号化]初期化ベクター:dc5141987f5e72ec928b300c8b3eacae Encrypted :00GHnQVmzHIPxkicbi6mwwr+PeUCOThy93/iSMZ/T+A= [復号]暗号化キー :71355b76c028d8a3816d6e05ab6d6669e70579944ef031fe490dd667fa49bcd1 [復号]初期化ベクター:dc5141987f5e72ec928b300c8b3eacae Decrypted :cleattext
Java(復号のみ)
Encrypted :00GHnQVmzHIPxkicbi6mwwr+PeUCOThy93/iSMZ/T+A= [復号]暗号化キー :71355b76c028d8a3816d6e05ab6d6669e70579944ef031fe490dd667fa49bcd1 [復号]初期化ベクター:dc5141987f5e72ec928b300c8b3eacae Decrypted :cleattext