アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

PHP:他言語でAES-128-CBC暗号化したものを復号化したら、意図せぬ「00」がパディングされて困った(備忘)

PHPの開発で、ちょっとめずらしいケースを経験しました。

f:id:arakan_no_boku:20180926203227j:plain

備忘録代わりに書いておこうかなと思います。 

 

まず、何をしようとしていたか

 

もともと。

PHPでURLパラメータで暗号化文字列だけを受け取って、復号してから処理をする。

こういうのを頼まれたわけです。

やりたいことは。

  1. 呼出側アプリ(VBとかのクラサバプログラム)で、ログインする。
  2. そのログインキーとタイムスタンプを事前認証で、PHP側に渡してDB登録する。
  3. 続けて、同じログインキーをつかってURLを発行してPHP側に渡す。
  4. PHP側で後から来たURLのパラメータのログインキーの一致と、前に登録された時刻と現在時刻と比較する。
  5. それで規定の時間以内の場合のみ認証OKとする。

そんな仕様です。 

その時渡されるURLパラメータは平文ではなく、クライアントプログラム側のプログラムでAES-128-CBCで暗号化してます。

なので。

今回の処理は、「VBとかのクライアント側プログラムで暗号化されたものを、PHP側で復号化する」というのが肝です。

めずらしいケースを経験したのは、このPHP側の処理を依頼されて作っていた時です。

 

単体・結合テストレベルでは万全だったんだけど。

 

PHPで暗号を復号化する理は、「openssl_decrypt(PHP: openssl_decrypt - Manual

)」を使えば、ごく簡単にかけます。 

ただ、今回は他の言語(VBJAVA)で暗号化した文字列をパラメータで受け取る点がちょっと気になる点ではありました。 

なので、念には念をいれて、事前に実際の処理で暗号化したものを受け取って、復号できることを単体テストで確認もしていたわけです。 

まあ、当然、開発しているPCでWEBサーバーを立ち上げて、そこでURLを生成して結合テストもやりました。 

結構、万全にやってたつもりでしたけど。 

発生した現象は次の2つです。

① デコードした結果に「00」がパディングされてしまう。

② 特定の変数の時だけ、デコードした結果がemptyになってしまう。

 

デコードした結果に「00]がバインドされてしまう

 

例えば。 

元文字列が「8C89A」、暗号化したものが「XZFupAX4LSDcZzXM6/zR」だとします。 

これを受け取って、PHPで復号化します。 

printで表示するとちゃんと「8C89A」と表示されます。 

ところが、これをDBアクセスのキーにするとDBに存在しているのに不一致になる。 

不思議に思ってダンプすると、「38 00 43 00 38 00 39 00 41 00」・・・。 

なんと、文字の間に「00」がパディングされてます。 

復号の仕方が悪いのかと思うと、復号する部分だけを別のソースに書いて確認するとそうならないわけです。 

なったり、ならなかったり・・。 

一番、困るパターンでした。 

で、しょうがないので、どう対応したかというと。 

preg_replace('/\x00/','',openssl_decrypt($encrypted_string, $method, $key, 0, $iv));


と、無理やり00を消すようにしました。 

これで問題は発生しなくはなりましたが・・完璧な対処療法ですね。

 

2018/01/18追記

上記の対応だと、MACアドレスに「0」が含まれていたら正しく復元できないのじゃないか・・との質問をいただきました。
なるほど・・でも、大丈夫です。
文字の「0」はHEXでは「x30」なので消えません。
HEXの「x00」は、恐らく有効な値としてはいってくることはないと思ってます。

 

特定の変数の時だけ、デコードした結果がemptyになってしまう

 

これは、もっと不思議な現象です。 

復号化処理を「get_decrypted_string」という関数にまとめました。 

それで以下のように書きました。

$decrypted_param = get_decrypted_string($encrypted_string);

 

なんら問題ないはずなんですが。 

なんどやっても、$decrypted_paramemptyになってしまうわけです。 

でも、もともと、他のソースで復号化できるかテストしていたコードのままなので、復号ができないわけはないです。 

違いは一つだけ。 

$dec = get_decrypted_string($enc);

 

としていた変数を、$decrypted_param に変更しただけ。 

それで、まさかと思って、変数名を$decに戻すと、すんなりOKになりました。 

これも、未だに気持ち悪いですが、結果オーライみたいになってます。 

$decrypted_param という変数が特殊な予約語とか言う情報も特にないし、なんなんでしょうね。

 

2018/01/18追記

ちなみに上記の2つの現象は、Windowsサーバ+Apatch2.4+PHP7のテスト環境では発生しません。
両方とも、リリースサーバーに移行した場合のみ発生します。
調べたいのですが、リリースサーバの管理は別会社に委託しているそうで、最低限の権限しかもらえておらず、PHP.iniを確認することもできません。
かつ、自分に依頼した人が、何故か管理してる会社に質問するのを嫌がっていて、リリースサーバのOSすら「LinuxではないUNIX系のなんからしい・・」としか教えてもらえないという(笑)・・実にトホホな状態です。
まあ、対処療法でOKになったから良いんですけど。

 

ついでに、他にひっかかったことも書いとこう

 

これはケアレスミスですが。
テストでうまくいってたのに、リリース環境にうつすとエラーになる。 

というのが、他にも発生したのですね。
理由はかんたん。 

時刻を比較して規定の時間が経過するとエラーになる処理で、time()で取得した現在時刻が8時間前の時刻になってました。
なんのことはない、サーバーのPHP.iniでタイムゾーンがデフォルトのままで設定されていたわけですね。
PHPタイムゾーンのデフォルトは日本じゃない・・ですからね。 

こういう事があるので、ソース側で、date_default_timezone_set('Asia/Tokyo');を明示的に呼んどかねばいけないなというのを忘れてました。 

最近は pythonとかJavaとかを使うことが多くて、PHPは久しぶりでしたけど、やっぱり思ってもみないことがおきますね。 

まあ、これが面白いところでもあるんですけど。