異なる言語間でのAES暗号化、復号に挑戦して「あと一歩!」という所で失敗した話。
参考ソースは下記URLから確認できます。
はじめに
URLのGETパラメータに機密情報を乗せる際、「平文はまずい」ということでAES暗号化を導入することになりました。 システムの都合上、送信側(Java)と受信側(Python)という異なる言語間での暗号化・復号が必要という、少し難易度の高いタスク。
結果から言うと、「あと一歩」のところでハマり、苦い経験をすることになりました。その記録を共有します。
開発フェーズ:完璧だと思ったローカル検証
まずは自分のローカル環境で、JavaのクラスファイルとPythonのスクリプトを準備しました。 AES-CBCモード、128bit鍵、IV(初期化ベクトル)は暗号文の先頭に付与。
// Java側で暗号化
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// ...中略...
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
# Python側で復号
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(encrypted_body))
実行してみると、Javaで暗号化した文字列がPythonで見事に復号されました。 「よし、あとはこれをWebアプリのロジックに組み込むだけだ」と、この時の私は確信していました。
絶望フェーズ:Webアプリに組み込んだ瞬間に動かない
いざ、JavaのWebアプリケーション側にソースコードを埋め込み、実際の画面からリクエストを飛ばしてみました。
すると、Python側の復号処理でエラーが発生。 「UnicodeDecodeError」や「Padding is incorrect」の嵐。
「うまくいった!」と思っても暗号化した文章ではない文字化けしたような文字列が出力される。。。
「さっきローカルのJavaファイルでやった時は動いたのに、なぜ?」
考察:あと一歩、何が足りなかったのか
結局、その時は時間切れで別の手法(POSTへの変更やトークン方式など)に切り替えることになりましたが、後から振り返ると以下の「Webアプリケーション特有の罠」にハマっていた可能性が高いと考えています。
-
URLエンコードの「二重の罠」
GETパラメータで送る際、Base64に含まれる + がURLデコードで半角スペースに化け、それをさらにPython側で受け取る際に変換がズレるという、文字列操作のミス。 -
文字エンコーディング(UTF-8 vs MS932/Shift_JIS)
Webサーバー(Tomcat等)の設定により、String.getBytes() がデフォルトの文字コード(Windows環境ならMS932など)を参照してしまい、Python側(UTF-8)と食い違っていた可能性。 -
GETパラメータの長さ制限
暗号化+Base64エンコードによって文字列が長くなり、ブラウザやサーバーの制限で末尾が欠損していた可能性。
まとめ:学んだこと
「単体テスト(ローカル)で通ったからといって、システム環境(Web)で通るとは限らない」という、基本にして最大の教訓を得ました。
特に異なる言語間での暗号化は、
バイト配列の扱い
パディング方式の厳密な一致
環境ごとのデフォルト文字コード
これらが一つでも狂うと成立しません。「あと一歩」を埋めるには、環境差異への深い理解が必要だと痛感した出来事でした。