読者です 読者をやめる 読者になる 読者になる

S_a_k_Uの日記みたいなDB

~サクゥーと呼ばないで~

PHPで暗号化された情報をJavaで復号する(ちょっと改造)

昨日(PHPで暗号化された情報をJavaで復号する - S_a_k_Uの日記みたいなDB)の続き。
初見で処理が判りにくかったので、シンプルなコードにしてみた。
それからストレッチング処理にも対応させてみた。こちらは変数名やコメントなんかも見直して、PHPJavaでソースを極力合せてみた。
#少し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