QA@IT

JavascriptとPHPの復号と暗号

19620 PV

プログラミング初心者です。
現在、AESを用いてJavaScriptで暗号化したデータをPHPで復号させることに悩んでいます。
参考サイトを見てコードを書いてみたのですが、
例えば"Message"という文字列を"Secret Passphrase"(PHP側と共有済み)という文字列を鍵とした場合JavaScriptでは、

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
    var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
<script>

だけでいいのでしょうか?
またその暗号化した文字列をPHPのPOSTで取得してからPHPではどのように復号すればいいのでしょうか?

もしよろしければご教授していただきたいです。
よろしくお願いします。

  • SSL/TLSを使えばいいんじゃないですか?
    -
  • mjunさんのコードで生成された暗号文が復号できるかどうか手元の環境(PHP 5.3)で試したらうまく行きましたけど、具体的にどこが問題になっていますか?
    -
  • 返事が遅れて申し訳ありません。現在は、Javascript側で平文をCBCモードで暗号化し、暗号文とivをpostしphp側に送っています。php側では取得した内容をCBCモードで復号しているのですが、正しく復号されていない状況です。 -

回答

復号自体はこんな感じです。

mjunさんのコードを実行すると変数encryptedにはbase64エンコードされた文字列が入っており、実行するたびに違う値になるようでした。

試しにbase64デコードすると、中身は"Salted__"で始まり、そのあとはバイナリデータになっていました。これはopensslのサンプルプログラム(openssl enc)のフォーマットです。8バイト目から8バイトのsaltが入っていて、16バイト目から暗号文が入っているはずです。毎回値が変わるのは、saltが毎回違うからだと推定できます。

これを復号するには、まず暗号アルゴリズム、鍵長、モード、パディングが必要ですが、crypto-jsのマニュアルを見ると、AES、256ビット、CBC、pkcs7だと書いてあります。PHP側の方ですが、mcryptを使おうとすると、MCRYPT_RIJNDAEL_256がありますが、これは鍵長とブロック長が256ビットです。AESは鍵長が256ビットでもブロック長は128ビットです。なのでmcryptを使うのは無理そうです。が、PHPのマニュアルを見るとOpenSSLもあり、そちらでaes-256-cbcを使えばよいことがわかります。

次に復号には鍵が必要ですが、パスワードベースの暗号化を使っているので、パスワード(or パスフレーズ)が必要ですが、mjunさんは「PHP側と共有済み」としているので、これはわかっているものとします。他に鍵導出アルゴリズム、salt、iteration回数、出力させたい鍵素材のバイト長などが必要です。crypto-jsのマニュアルのInteroperabilityの節を見ると、鍵導出はOpenSSL互換であるようなことが書いてあります。

したがって鍵導出は(1)OpenSSL独自方式EVP_BytesToKey、(2)PBKDF1、(3)PBKDF2のどれかです。これは若干リサーチが必要ですが、調べると(1)だとわかります。(1)はハッシュアルゴリズムも必要ですが、md5だとわかります。iterationは1回だとわかります。このあたりもマニュアルからすぐにはわからないので若干リサーチが必要です。出力長は48バイトです。なぜなら鍵は32バイトであり、ivは16バイト(ブロック長)だからです。saltは先ほど見た通りencryptedをそのままPOSTすれば、PHP側に渡ってきます。

というわけで、PHPでこんな感じの関数を作れば復号できます。javascriptからは変数encryptedをPOSTし、それをdecrypt()の第2引数に渡すものとします。

function decrypt($password, $encrypted) {
    $encryptedをbase64デコードする;
    $encryptedから$saltと暗号文($cipher)を切り出す;
    $passwordと$saltを使って鍵導出($keyと$ivを得る);
    openssl_decrypt($cipher, 'aes-256-cbc', $key, true, $iv);
}

しかしEVP_BytesToKey相当の鍵導出をPHPで自作しなければならない点が面倒です。とりあえず実験するだけなら、PHPのマニュアルのopenssl_decryptのところをよく見てください。下のほうにUser Contributed Notesがあって、class sqAESが見つかりますが、参考になると思います。僕はこれを見てやっつけの検証プログラムを書きましたが、5分くらいでできました(恥ずかしいコピペプログラミングです)。真面目にやるともっと時間がかかると思います。

復号はこれでできますが、気になるのはパスフレーズをjavascript側とPHP側とで共有する前提です。パスワードやパスフレーズは人間が記憶しておくものなので、一体どうやって共有するんだろう?と思いました。javascript側はユーザに入力させればよいと思うのですが、PHP側はサーバサイドだと思うので、ユーザが入力したパスフレーズをサーバに送らなければ共有できないように思われます。するとそのパスフレーズを暗号化しなければなりませんが、それをどうやってやるんだろう?ということです。

そのためにはさらにそれを暗号化するためのパスフレーズが必要ではないでしょうか?が、今度はそのパスフレーズを暗号化するためのさらなるパスフレーズが必要になり、無限ループになるんじゃないかと思いました。他にパスフレーズをプログラムに直接埋め込む手もありますが、第三者にパスフレーズが見えてしまうと暗号化が台無しになる恐れがあります。なので最初のコメントで「SSL/TLSを使えばいいんじゃないですか?」と書いた次第です。

以上です。では。

編集 履歴 (0)

暗号関連では誤解しやすいところなのですが、暗号処理の仕方というのは、暗号アルゴリズムとパスワード等が決まっただけで確定するものではありません。
暗号モードになにを使うか、キーやIVをどう生成するか、それらに必要な情報をどうエンコードして結果に埋め込むか、あるいは埋め込まないかなど、暗号処理のスキーム一式をどうするかの決めごとが必要です。
件のJSライブラリも、何らかの仕様で、あるいはカスタマイズ可能な方法でこれらの仕組みを決めているはずです。
連携するアプリケーションでは、これらの仕組みを把握した上で、それに則って実装を行う必要があります。
互換性のある方式が共通で提供されているなら、双方でそれを使う事もできます。
件のライブラリは、例えばOpenSSLの方式とは連携できるような感じですね。

編集 履歴 (0)
  • 返事が遅れて申し訳ありません。アドバイスを元にもう一度書き直してみます。 -

phpで復号化したいのであれば、単純にphpで書けばいいだけなのでは?

mcrypt_decrypt('MCRYPT_RIJNDAEL_128','Secret Passphrase',encrypted,'ofb','')

ちなみに、AESは、Rijndelの一部と考えて問題ないので、上記のようなコードになります。

参考まで
http://php.morva.net/manual/ja/function.mcrypt-decrypt.php
http://php.net/manual/ja/mcrypt.ciphers.php

編集 履歴 (1)
  • アドバイスありがとうございます。実際に試してみたのですが正しく復号されませんでした。 -

暗号化と同様にjavascriptで、復号化すればいいだけでは?

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
    var decrypted = CryptoJS.AES.decrypt( encrypted , "Secret Passphrase");
<script>

エンコードが絡む場合があるかもです。

編集 履歴 (0)
  • アドバイスありがとうございます。
    現在、クライアント側(JavaSCript)で暗号化させたデータをサーバ側(php)で取得し復号させることを考えています。
    そのためこのような考えに至っているのですが、
    やはり考えが間違っているのでしょうか?
    もしよろしければご教授お願いします。
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。