"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

素のPHP:AES-128-CBC暗号化文字列を復号化時の意図せぬ「00」パディング対策(備忘)

PHPで暗号化した文字列をURLパラメータで受け取って復号化したら、意図せず「00」パディングされて正しく処理できない・・という問題に対応したときのメモです。

f:id:arakan_no_boku:20180926203227j:plain

 

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

 

PHPでURLパラメータで暗号化文字列だけを受け取って、復号する機能です。

ログイン情報を平文で流したくないので、必要な情報をパッケージにして、AESで暗号化した文字列をURLパラメータで渡し、受け取ったプログラムで複合化してから、ログイン認証処理をする・・という仕様です。

暗号化には、AES-128-CBCを使いました。

なお、暗号化自体はPHP限定ではなく、VBとかJavaとか他の言語でされる場合もあります。

 

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

 

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 という変数が特殊な予約語とか言う情報も特にないし、なんなんでしょうね。

これはいまだに、よくわかりませんが、その後再発もしていません。

ちなみに、本番サーバーのOSはWindowsではありません。

だから・・なんですかね?

でも、こちらからは、許された権限の範囲(デプロイできるだけ・・)しか確認する術がないので、詳しい設定とかを突っ込むことはできてません。

 

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

 

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

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

時刻を比較して規定の時間が経過するとエラーになる処理で、time()で取得した現在時刻が8時間前の時刻になってました。
本番サーバーのPHPタイムゾーンがデフォルトのままだったそうです。

考えてみれば当然で。

別に日本限定ではないですもんね。
こういう事があるので。

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

 

いろいろありますね。

ではでは。