androidとGAEでRSA暗号

今作っているアプリでRSA暗号を使おうとしているんですが、ハマりました。
問題はAndroidで暗号化した文字列をGAE(Mac)で復号化しようとすると、復号できないというもの。
そもそもテスト用に作ったMacjavaプログラムで暗号化したデータと、Androidで暗号化したデータが一致しない。
同じ文字列を同じキー値で暗号化したならば同じ暗号データが手に入るはず
パディングにランダムな文字列を使えば異なるデータになります
なぜか。
Mac(GAE)のJDKに搭載されているJCE(暗号化拡張機能)とAndroidに搭載されているJCEのプロバイダが異なるためのよう。
プロバイダが異なっていても、モード・パディングを揃えれば暗号化/復号化できます
JCEってのはざっくりいうと暗号化/復号化エンジンのインターフェイスみたいなもの*1
実装されているJCEの事をプロバイダって言う。
それぞれどんなプロバイダが搭載されているかリストしてみた。

検証コード

for (Provider provider : providers) {
  System.out.println(provider.getName());
  System.out.println(provider.getInfo());
  System.out.println();
}

GAE(Mac JDK1.6)

  • SunPKCS11-Darwin
    • SunPKCS11 accessing Mac OS X SmartCardServices
  • SUN
    • SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore; PKIXCertPathValidator; PKIX CertPathBuilder; LDAP, Collection CertStores, JavaPolicy Policy; JavaLoginConfig Configuration)
  • Apple
  • SunRsaSign
    • Sun RSA signature provider
  • SunJSSE
    • Sun JSSE provider(PKCS12, SunX509 key/trust factories, SSLv3, TLSv1)
  • SunJCE
    • SunJCE Provider (implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, Diffie-Hellman, HMAC)
  • SunJGSS
    • Sun (Kerberos v5, SPNEGO)
  • SunSASL
    • Sun SASL provider(implements client mechanisms for: DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5; server mechanisms for:DIGEST-MD5, GSSAPI, CRAM-MD5)
  • XMLDSig
    • XMLDSig (DOM XMLSignatureFactory; DOM KeyInfoFactory)
  • SunPCSC
    • Sun PC/SC provider

Android 1.6 - Android2.1-update1(emulator)

  • DRLCertFactory
    • ASN.1, DER, PkiPath, PKCS7
  • Crypto
    • HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)
  • HarmonyJSSE
    • Harmony JSSE Provider
  • BC
    • BouncyCastle Security Provider v1.34

これを見ると

Android側で暗号化するときはBC(BouncyCastle)を使って暗号化して、GAE(Mac)側ではSunJCEを使って復号化しようとすると思われます。
そんなことありません
じゃぁ、GAE側もBCで復号化すればいいじゃないかと思ってコードを書き直します。

BCプロバイダをダウンロードする
bouncycastle.org
http://www.bouncycastle.org/

war/WEB-INF/libに配置してパス通す

Cipherインスタンスを作るコード
Cipher cipher = Cipher.getInstance(getCRYPT_ALGORITHM());
cipher.init(Cipher.DECRYPT_MODE, privateKey);

Cipher cipher = Cipher.getInstance(getCRYPT_ALGORITHM(), new org.bouncycastle.jce.provider.BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, privateKey);

これで無事復号化できました。

追記 2010-09-01

BC-SunJCEで暗号化/復号化します。
ポイントはアルゴリズムとモードとパディングを同じにすることで暗号化/復号化することができます。
変更する箇所はCipherのインスタンスを作るところ

// 受け渡す文字列は アルゴリズム/モード/パディング の形式で
Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding");

これを暗号化するとき・復号化するときにCipherのインスタンスを作るところで指定してあげれば正常に動作します。
このモードとパディングはいくつか種類があって、暗号化の種類によって指定できるできないが決まっているみたいです。
SunJCEに指定できるパラメータについては、書いてあるページを教えていただきました

Java 暗号化アーキテクチャー Sun プロバイダのドキュメント
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/security/SunProviders.html#SunJCEProvider

BCについてはページを見つけることができませんでした…。

まとめ

同じJCEが安心
暗号化/復号化するときのプロバイダの指定はデフォルトじゃなくて、ちゃんと指定した方がいい。
androidにはデフォルトでBCが入ってたけど、入っているっていう保証はなさそうな気がするからプロバイダをアプリに入れておいた方がいい気がする。
今回GAE(mac)と言ってたのは実際にデプロイしてたわけじゃないから、GAEにはまた別のプロバイダが入ってたりするんじゃないかなぁ…。まさかAppleプロバイダが入ってるとは思えないし。
結局自分でプロバイダ用意した方がいいと思う。

上は打ち消してますけど、自分でプロバイダ用意しようかと思います。
GAEはまだいいにしてもAndroidについては環境が端末の種類だけあるわけですから
プロバイダがないですなんて状態になったら困ります。
暗号アルゴリズムとモードとパディングを合わせましょう。

*1:合ってる??