gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

JavaScript/PHPで暗号化と復号

はじめに

暑い日が続いたかと思うと梅雨のように雨が続く。今年の夏は厳しくなりそうだと今から気が滅入っているgrnishiです。こんにちは。

本題

クライアント側で暗号化し、サーバ側で復号する。またはサーバ側で暗号化したテキストをクライアント側で復号する。といった需要はそこそこあると思います。

同じ言語(ライブラリ)でやれば簡単に実装できるのですが、異なる言語間だとイマイチうまくいかない事が多々あります。

というわけでニッチな需要に応えるべく、サンプルを作成しました。

ちなみにニッチの語源は英語で「niche」です。建築業界では窪みなどを指す言葉だそうです。

環境

JavaScript側はこれを使います。

crypto-js https://code.google.com/archive/p/crypto-js/

PHP側はこれを使います。

openssl_encrypt https://www.php.net/manual/ja/function.openssl-encrypt.php

openssl_decrypt https://www.php.net/manual/ja/function.openssl-decrypt.php

暗号メソッドはAES-256-CBC

コード

JavaScript側

/**
 * 暗号化
 */
  function encrypt(plain, passphrase){
    var salt = CryptoJS.lib.WordArray.random(128);
    var iv = CryptoJS.lib.WordArray.random(16);
    var key = CryptoJS.PBKDF2(passphrase, salt, { hasher: CryptoJS.algo.SHA512, iterations: 100, keySize: 8 });
    var cipher = CryptoJS.AES.encrypt(plain, key, {iv: iv});
    var data = {
        cipher : CryptoJS.enc.Base64.stringify(cipher.ciphertext),
        salt : CryptoJS.enc.Hex.stringify(salt),
        iv : CryptoJS.enc.Hex.stringify(iv)    
    }
    return data;
  }

/**
 * 復号
 */
  function decrypt(cipher, passphrase, salt, iv){
    var key = CryptoJS.PBKDF2(passphrase, CryptoJS.enc.Hex.parse(salt), { hasher: CryptoJS.algo.SHA512, iterations: 100 , keySize: 8});
    var plain = CryptoJS.AES.decrypt(cipher, key, { iv: CryptoJS.enc.Hex.parse(iv)}).toString(CryptoJS.enc.Utf8);
    return plain;
  }

PHP側

<?php
/**
 * 暗号化
 */
  function encrypt($plain, $passphrase) {
    $salt = openssl_random_pseudo_bytes(128);
    $iv = openssl_random_pseudo_bytes(16);
    $iterations = 100;
    $key = hash_pbkdf2("sha512", $passphrase, $salt, $iterations, 128);

    $cipher = openssl_encrypt($plain, "AES-256-CBC", hex2bin($key), OPENSSL_RAW_DATA, $iv);
    $data = [
      "cipher" => base64_encode($cipher),
      "salt" => bin2hex($salt),
      "iv" => bin2hex($iv),
    ];
    return $data;
  }

/**
 * 復号
 */
  function decrypt($cipher, $passphrase, $salt, $iv){
    $iterations = 100;
    $key = hash_pbkdf2("sha512", $passphrase, hex2bin($salt), $iterations, 128);
    $plain= openssl_decrypt(base64_decode($cipher), "AES-256-CBC", hex2bin($key), OPENSSL_RAW_DATA, hex2bin($iv));
    return $plain;
  }

結果

JavaScriptで暗号化する

var plain = "あらゆる現実をすべて自分のほうへねじ曲げたのだ";
var passphrase = "passphrase";
var data = encrypt(plain, passphrase);

//
data.cipher
lj57hl3KarnYV2NFZHZVpcrh4Z1rK3aSvolRdrzTimPZ8DzolSDn3bOkgn9rBKNdIx4vs/eV+vASjFQJQs32Wwj8BZUUwhv3+wYm6nombAw=

data.salt
38b1576dbcf429da7acd75fdec89d255d4c49e856ce46ca9a9fea4231e4c03b6195ecd5daecd95530ee5a37203fbe7478171f118266ad549563d69e23ec8909dbf2557af2ad6195655e7418023d582b970d6620d3ef770db6c3f0a4427140ceb254b8edc281a44545d3e64a0c2e7256f661df9f32168870bd046612ea8bac6b3

data.iv
fa6ed5055e008e19414c6dca4eaae8a3

PHPで復号する

<?php
$cipher = "lj57hl3KarnYV2NFZHZVpcrh4Z1rK3aSvolRdrzTimPZ8DzolSDn3bOkgn9rBKNdIx4vs/eV+vASjFQJQs32Wwj8BZUUwhv3+wYm6nombAw=";
$salt = "38b1576dbcf429da7acd75fdec89d255d4c49e856ce46ca9a9fea4231e4c03b6195ecd5daecd95530ee5a37203fbe7478171f118266ad549563d69e23ec8909dbf2557af2ad6195655e7418023d582b970d6620d3ef770db6c3f0a4427140ceb254b8edc281a44545d3e64a0c2e7256f661df9f32168870bd046612ea8bac6b3";
$iv = "fa6ed5055e008e19414c6dca4eaae8a3";
$passphrase = "passphrase";
$plain = decrypt($cipher, $passphrase, $salt, $iv);

//
あらゆる現実をすべて自分のほうへねじ曲げたのだ

PHPで暗号化する

<?php
$plain = "あらゆる現実をすべて自分のほうへねじ曲げたのだ";
$passphrase = "passphrase";
$data = encrypt($plain, $passphrase);

//
$data["cipher"]
ZP4b2C3wBwz7eInfcs6r5CHKCZ2C1O/zpxA4w7u+Yf899dOjsO9c07OgYQZ99Su7BjW5gnDahrjuoqLbvH+PB5FCh3361q+ZhR3sX5R55l4=

$data["salt"]
8bcdbf1e97e3df12ec23b52f518b7652dac332f466893065b4a18e1ba050057e592e3c53e0d33e97fd79d6cd672376b021264fda786fce8b3554de3b230e75cd5e51cfdc88e217e03a6b488530f4d25f21c229122fde2f5ad43b04001925b0ce18b4f9d8269b37a2b84f09bb4b986b2ad25216492438cd9c195e7f8c2cacd5f1

$data["iv"]
9e7498abc461754140b9700a5c73efa7

JavaScriptで復号する

var cipher = "ZP4b2C3wBwz7eInfcs6r5CHKCZ2C1O/zpxA4w7u+Yf899dOjsO9c07OgYQZ99Su7BjW5gnDahrjuoqLbvH+PB5FCh3361q+ZhR3sX5R55l4=";
var salt = "8bcdbf1e97e3df12ec23b52f518b7652dac332f466893065b4a18e1ba050057e592e3c53e0d33e97fd79d6cd672376b021264fda786fce8b3554de3b230e75cd5e51cfdc88e217e03a6b488530f4d25f21c229122fde2f5ad43b04001925b0ce18b4f9d8269b37a2b84f09bb4b986b2ad25216492438cd9c195e7f8c2cacd5f1";
var iv = "9e7498abc461754140b9700a5c73efa7";
var plain = decrypt(cipher, passphrase, salt, iv);

//
あらゆる現実をすべて自分のほうへねじ曲げたのだ

さいごに

JavaScriptで暗号化→PHPで復号、PHPで暗号化→JavaScriptで復号をやりました。(当然ですが)言語が異なっていても問題なく復号できます。

node.jsやBunなりでサーバサイドもJavaScriptで書けばこんな苦労は無いんですけどね。

大人の事情でそうもいかない人のためになれば幸いです。