Hatena::ブログ(Diary)

明日の鍵

2010-08-31

androidとGAEでRSA暗号

今作っているアプリでRSA暗号を使おうとしているんですが、ハマりました。

問題はAndroidで暗号化した文字列をGAE(Mac)で復号化しようとすると、復号できないというもの。

そもそもテスト用に作ったMacのjavaプログラムで暗号化したデータと、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
    • Apple Provider (implements DES, Triple DES, AES, Blowfish, PBE, Diffie-Hellman, HMAC/MD5, HMAC/SHA1)
  • 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:合ってる??

2009-12-23

メッセージ送受信機能を作りました。

概要

Androidアプリ内で例外をキャッチしたときのログをGoogleAppEngineにためてメールで知らせます。

ついでなんで例外だけじゃなくて普通のメッセージを送信できるようにしてみました。

ダウンロード

Android
  • メッセージ送信機

http://bit.ly/5cPFJc

GAE/J
  • メッセージ受信機

http://bit.ly/5w0Wcm

ライブラリファイルは除外してあります

GAE/Jの通常のライブラリの他にjsonicのライブラリも必要です

jsonic - simple json encoder/decoder for java

今回使用したバージョンは1.1.2です

データ設計

やり取りするデータの型はJSONです。

メッセージの種類は通常メッセージと例外メッセージの二つです。

通常メッセージ
{
  AppName:アプリケーション名 , 
  AppVersion:バージョン , 
  Tag:タグ , 
  SendMail:メール送信フラグ , 
  Message:メッセージ
}

メール送信フラグはfalseにするとメールを送信しません

例外メッセージ
{
  AppName:アプリケーション名 , 
  AppVersion:バージョン , 
  Tag:タグ , 
  SendMail:メール送信フラグ , 
  Message:メッセージ
  StackTrace:{
    スタックトレース
  }
}

メール送信フラグはfalseにするとメールを送信しません

メッセージはException#getMessage()の値

スタックトレースはException#getStackTrace()の値

準備

そのままでは使えません。

実際にアプリに使う場合はいくつか設定するポイントがあります

メッセージ送信機
9:public static final String TAG = "MessageSender";

アプリ名でも入れておきます

  • MessageFactory.java
 9:private static final String APP_VERSION = "1.0.0";
10:private static final String APP_NAME = "MessageSender";

バージョンとアプリ名を入れます。

自動的に設定できるようにしたかったんですけど、引数を増やすのが面倒かなと思い定数にしました。

  • SendData.java
6:private final String MESSAGE_URL = "http://*****.appspot.com/messagereceiver";
7:private final String EXCEPTION_MESSAGE_URL = "http://*****.appspot.com/exceptionmessagereceiver";

"*****"の部分にGAEのアプリケーション名を入れます

メッセージ受信機
  • MailSender.java
18:private final static String MAIL = "*****";
19:private final static String NAME = "tomoki yamashita";

自分自身のメールアドレスの名前を入れます。名前は適当でおk

  • appengine-web.xml
3:<application>*****</application>

"*****"にGAEのアプリケーション名を入れます

問題

当然だけど、キャッチした例外しか報告できないのが難点

一番知りたいのはキャッチできなかった例外のことなんで、

これじゃバグ報告機能として完全ではないのではないかと思っています。

あと、バージョン番号とかアプリ名とか自動的に設定されるようにしたいなー

2009-12-07

Google App Engine for Java で ローカルのデータストアを削除する

ローカルでテストしているとデータストアの内容を消したかったんだけどやり方がわからなくて

教えてもらったのでメモ

/war/WEB-INF/appengine-generated/local_db.bin を削除する

2009-12-06

Google App Engine for Java で CRUD

次はGAE/Jを使ったAndroidアプリを作りたいので、GAE/Jの勉強中。

とりあえず簡単なCRUD作ってみました。

ソース

PMF.java

PersistenceManagerFactoryを保持するユーティリティクラス

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

public class PMF {
  private static final PersistenceManagerFactory INSTANCE = JDOHelper.getPersistenceManagerFactory("transactions-optional");

  public static final PersistenceManagerFactory get() {
    return INSTANCE;
  }

  public static final PersistenceManager createManager() {
    return get().getPersistenceManager();
  }

  private PMF() {
  }
}
Content.java

コメントを保持するエンティティクラス

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Content {
  @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  @PrimaryKey
  private Key key;
  @Persistent
  private String title;
  @Persistent
  private String comment;

  public Content() {

  }

  public Content(Key key, String title, String comment) {
    this.key = key;
    this.title = title;
    this.comment = comment;
  }

  public Key getKey() {
    return key;
  }

  public void setKey(Key key) {
    this.key = key;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getComment() {
    return comment;
  }

  public void setComment(String comment) {
    this.comment = comment;
  }
}
CommentDAO.java

CRUDするクラス

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.annotations.Transactional;

public class CommentDAO{

  @Transactional
  public boolean addContent(Content content) {
    PersistenceManager pm = PMF.createManager();
    try {
      pm.makePersistent(content);
      return true;
    } catch (Exception e) {
      return false;
    } finally {
      pm.close();
    }
  }

  @Transactional
  public boolean updateContent(Content content) {
    PersistenceManager pm = PMF.createManager();
    try {
      Content persistenctContent = pm.getObjectById(Content.class, content.getKey());
      persistenctContent.setTitle(content.getTitle());
      persistenctContent.setComment(content.getComment());
      return true;
    } catch (Exception e) {
      return false;
    } finally {
      pm.close();
    }
  }

  @Transactional
  public boolean deleteContent(Integer id) {
    PersistenceManager pm = PMF.createManager();
    try {
      Content persistenctContent = pm.getObjectById(Content.class, id);
      pm.deletePersistent(persistenctContent);
      return true;
    } catch (Exception e) {
      return false;
    } finally {
      pm.close();
    }
  }

  @Transactional
  @SuppressWarnings("unchecked")
  public List<Content> getContentList() {
    PersistenceManager pm = PMF.createManager();
    try {
      String query = "select from " + Content.class.getName();
      Collection<Content> result = (Collection<Content>) pm.newQuery(query).execute();
      return new ArrayList(pm.detachCopyAll(result));
    } finally {
      pm.close();
    }
  }
}

とりあえず

動きます。

おかしい所があったら教えてください。

参考

Google App Engine for Java [実践]クラウドシステム構築

まだデータストアの最初くらいしか読んでないですが、とても参考になります。

Google App Engine for Java (GAE/J) で Bigtable CRUDを行う Flex アプリ作成法 - hamadakoichi blog

http://d.hatena.ne.jp/hamadakoichi/20090611/1244673675