2011年8月15日月曜日

Androidアプリ開発日記(2011/08/14)

今日はAndroidだけでなくJavaに関する話題.
JavaがStringをどう扱っているかということを考える必要がある.
Base64エンコードを行うとき,2バイト文字を入れると文字コードによってコンバート結果が異なる.

Javaではファイルを読み込むときに文字コードを指定することができる.
しかしそれはファイルを読み込む時点でそのファイルの文字コードを指定しているだけで,指定した文字コードで文字列を保持するわけではないようだ.
では読み込まれた文字列の文字コードはどうなっているのか.

結論だけ見ると,どうやらJavaが起動しているOSが使用している文字コードになるみたい.
その証拠にwindowsでエンコードするとShift-JISでエンコードされたものとなり,AndroidでエンコードするとUTF-8でエンコードされたものとなる.(Android OSの文字コードがutf-8であることより)

つまり,OSによって採用している文字コードは異なるので,何も考えずStringをBase64エンコードするとそれぞれ異なる結果が得られてしまう.それの吸収は次のようにすることである.(両者ともutf-8で統一するとする)

まずはwondowsでのやりかた
String str = ・・・
String encrypted = new BASE64Encoder().encodeBuffer(str.getBytes("utf-8"));

次にandroidでのやりかた
String str = ・・・
String encrypted = new String(Base64.encode(str.getBytes("utf-8")));
ようは変換したい文字列をエンコードするときに指定したい文字コードでbyte列を作ることによって同一の結果を得ることができるようになる.
デコードも同じようにすることで同じデコード結果を得ることができるよー.

ラベル: ,

2011年8月8日月曜日

androidアプリ開発日記(2011/08/07)

今回は通信(SFTP)について.
ファイルをやり取りしようとしたら,まぁSFTPかなという感じを覚える.
ではAndroidアプリでSFTPはできるのだろうか?A.もちろんできる.

元はJDK用に開発されているJSchというライブラリを使用することで解決可能.
筆者が採用している認証方式は公開鍵暗号方式なので,
まずは鍵を生成する.
以下のような感じで.
File keyfile = new File("id_rsa");

final int type = KeyPair.RSA;
final String pass = "";
try {
 KeyPair keyPair = KeyPair.genKeyPair(jsch, type);
 keyPair.setPassphrase(pass);
 keyPair.writePrivateKey(keyfile.getAbsolutePath());
 keyPair.writePublicKey(keyfile.getAbsolutePath() + ".pub",
   <コメント>);
 keyPair.dispose();
} catch (JSchException e) {
} catch (FileNotFoundException e) {
} catch (IOException e) {
}

はい.じゃあ繋げてみよう.手順としてはこう.
  1. JSchインスタンスの生成.
  2. 鍵の登録
  3. セッションの取得
  4. ChannelSftpインスタンスの生成
  5. セッションの取得
  6. やりたい処理
  7. 後処理
サンプルコードはこんな感じかな.
/********* 1 *********/
JSch jsch = new JSch();
/*********************/
/********* 2 *********/
try {
 jsch.addIdentity("data/data/" + this.getPackageName()
   + "/files/id_rsa");
} catch (JSchException e) {
 return;
}
/*********************/
/********* 3 *********/
Session session = null;
try {
 session = jsch.getSession("takefumi", "netherworld.dip.jp", 22);
} catch (JSchException e) {
 return;
}
/*********************/
session.setUserInfo(new UserInfo() {
 @Override
 public void showMessage(String arg0) {
 }
 @Override
 public boolean promptYesNo(String arg0) {
  return true;
 }
 @Override
 public boolean promptPassword(String arg0) {
  return true;
 }
 @Override
 public boolean promptPassphrase(String arg0) {
  return true;
 }
 @Override
 public String getPassword() {
  return null;
 }
 @Override
 public String getPassphrase() {
  return "";
 }
});
/********* 4 *********/
try {
 session.connect();
} catch (JSchException e) {
 return;
}
/*********************/
/********* 5 *********/
ChannelSftp sftp = null;
try {
 sftp = (ChannelSftp) session.openChannel("sftp");
 sftp.connect();
} catch (JSchException e) {
 session.disconnect();
 return;
}
/*********************/
/********* 6 *********/
やりたい処理
/*********************/
/********* 7 *********/
sftp.disconnect();
session.disconnect();
/*********************/

最後に問題となるのが鍵の置き場所.見える場所に置いておけば置換が楽になるけど,
他のアプリからもアクセス可能になってしまうため少々危険.
かといってassetsやアプリ用の領域に置こうとすると,ファイルを置くための処理をアプリに実装せねばならないし,assetsの場合は取得できるのがInputStreamのみという素敵設計.
「じゃあ鍵を暗号化しちゃえばいいんでね?」という案も通らない.
JSchが鍵のファイルへのパスを要するからである.
うーん.androidではID+PASSで通信設計したほうが安全なのかな?

ラベル: ,

2011年8月6日土曜日

Androidアプリ開発日記(2011/08/05)

これまでは開発に困った部分や疑問点を書いてきましたが,これはタダの日記.

PMの暗号化方式を変更して,PM4Aの読み込み機能の実装がほぼ終了.
PMによって暗号化したpasswordファイルをPM4A側で複合化できることも確認.
まだまだ使い勝手は悪いけどプロトタイプとしては耐えられると期待するところまでは来たはず.

逆にココまで来たからやらなくてはならなくなったことも多々あるけど・・・.
・PMパッケージのReadMe作成.
・PMパッケージのリリース

・PM4AとPM間の機能の差の吸収.
・レイアウトの検討
・可能な限りの各デバイスでのチューニング(主に縦横比,ディスプレイサイズなどに対する)
・同期における通信方の検討




にゃー(;´∀`)>
終わりが見えねぇや.
それが面白いと思ってしまう私はきっと兵隊予備軍・・・

androidアプリ開発日記(2011/08/05)

アプリの使用上,パスワード入力をするための機構が必要になった.
そこで,入力した文字列が見えないようなテキストボックスを定義することにした.
まずは入力した文字列が「・」で表示されるように以下のようにEditTextクラスを定義.
EditText text = new EditText(this);
text.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | text.getInputType());
ここでポイントとなるのが,text.getInputType()を足しているところ.
これによって,パスワード入力時に文字列補間機能が働いてしまい,パスワードが駄々漏れになることを防ぐことができる.

また,このままだとenterキーが入力されると改行文字がパスワードの文字列として追加されてしまう.
それはマズいので次にそこを直す.こんな感じ.
EditText text = new EditText(this);
text.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | text.getInputType());
text.setSingleLine(true);

やった!これでできた...と思ったら...
なんと入力文字列が「・」で表示されずにplane textとして表示されているではないかΣ(○Д○)
この問題はこうすると直る...みたい(結果論).
EditText text = new EditText(this);
text.setSingleLine(true);
text.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | text.getInputType());

順番入れ替えるだけで直るとか少し怖いけど,まぁちゃんと動くようなので良しとしよう.

ラベル:

2011年8月5日金曜日

Javaによる暗号化・複合化についての云々

今回はなんか釈然としないことについて.
Cipherクラスを使った暗号化・複合化を行う際,故意に暗号化したものとは違う鍵で複合化したとする.
そうすると何故かBadPaddingExceptionを返してくれる.
しかもエラーの内容は「Given final block not properly padded」とか言いやがります.もちろん暗号化する際にはpaddingしているにもかかわらず.毎回こんな風になるんならPaddingする意味って何なのさ!

一応今回テストで作ったプログラムを以下に記載.
AES+ECB+PKCS5Paddingの組み合わせで暗号化・複合化を行うもの.
public static void main(String[] args) {
  String data = "encryption and decryption test";
  String key = "aaaaa";
  byte[] enc = encrypt(data.getBytes(), key);
  String dec1 = new String(decrypt(enc, key));
  System.out.println(dec1);
  key = "bbbbb";
  String dec2 = new String(decrypt(enc, key));
  System.out.println(dec2);
 }

 private static byte[] encrypt(byte[] data, String key) {
  byte[] res = null;
  try {
   MessageDigest mg = MessageDigest.getInstance("MD5");
   mg.update(key.getBytes());
   SecretKeySpec sks = new SecretKeySpec(mg.digest(), "AES");
   Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
   cipher.init(Cipher.ENCRYPT_MODE, sks);
   byte[] encrypted = cipher.doFinal(data);
   res = encrypted;
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
  } catch (NoSuchPaddingException e) {
   e.printStackTrace();
  } catch (InvalidKeyException e) {
   e.printStackTrace();
  } catch (IllegalBlockSizeException e) {
   e.printStackTrace();
  } catch (BadPaddingException e) {
   e.printStackTrace();
  }
  return res;
 }

 private static byte[] decrypt(byte[] data, String key) {
  byte[] res = null;
  try {
   MessageDigest mg = MessageDigest.getInstance("MD5");
   mg.update(key.getBytes());
   SecretKeySpec sks = new SecretKeySpec(mg.digest(), "AES");
   Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
   cipher.init(Cipher.DECRYPT_MODE, sks);
   byte[] encrypted = cipher.doFinal(data);
   res = encrypted;
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
  } catch (NoSuchPaddingException e) {
   e.printStackTrace();
  } catch (InvalidKeyException e) {
   e.printStackTrace();
  } catch (IllegalBlockSizeException e) {
   e.printStackTrace();
  } catch (BadPaddingException e) {
   e.printStackTrace();
  }
  return res;
 }

ラベル: ,

2011年8月3日水曜日

Androidアプリ開発日記(2011/08/02)

Androidアプリにおいて項目を表示したいときはListViewを使うといいみたい.
ListViewの利点としては,コレと組み合わせて使うAdapterを上手く調整すれば,いろんなレイアウトの項目を作ることができるということ.
まずはベーシックな形から見てみましょう.

ListView view = new ListView(this);

ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1);
adapter.add("aaa");
adapter.add("bbb");
adapter.add("ccc");
view.setAdapter(adapter);
this.setContentView(view);
上のように作ったときの結果が次の図の感じ.
文字列から直接項目を作りたいときなどはこういう風にすれば簡単に作ることができる.

次にもう少し凝ったレイアウトを作ってみよう.
例えば,各項目の左端に図を表示し,かつ項目の文字列をその右側に持つようなもの.
このためにはまずは専用のAdapterを作成する.
public class ExAdapter extends BaseAdapter {
Context context;
List list;

/**
* コンストラクタ
*/
public ExAdapter(Context context) {
this.context = context;
this.list = new ArrayList();
 }

 public void add(String str) {
  this.list.add(str);
 }

 @Override
 public int getCount() {
  return this.list.size();
 }

 @Override
 public Object getItem(int position) {
  return this.list.get(position);
 }

 @Override
 public long getItemId(int position) {
  return position;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  ViewHolder holder = null;
  if (convertView == null) {
   LinearLayout layout = new LinearLayout(this.context);
   layout.setOrientation(LinearLayout.HORIZONTAL);
   holder = new ViewHolder();
   holder.image = new ImageView(this.context);
   holder.view = new TextView(this.context);
   layout.addView(holder.image);
   layout.addView(holder.view);
   holder.image.setImageResource(R.drawable.icon);

   convertView = layout;
   convertView.setTag(holder);
  } else {
   holder = (ViewHolder) convertView.getTag();
  }
  holder.view.setText(this.list.get(position));
  return convertView;
 }

 private class ViewHolder {
  ImageView image;
  TextView view;
 }
}

あとはこのクラスをさっき使っていたArrayAdapterの代わりに使えば,
デフォルトで提供されているiconイメージを使って画像付きの項目を作ることができる.
結果はこんな感じ.
こんな風に作れば同じレイアウトで項目を作ったとしても画像による識別が可能になるから便利だよね.
ということで今回は少々お役立ち情報でした...

ラベル:

2011年8月1日月曜日

Androidアプリ開発日記(2011/08/01)

Androidで使用されている暗号化アルゴリズムを調べてみたのでその結果を載せる.
やり方は,
以下のような感じで
for(Provider p : Security.getProviders()){
   for(Service s : p.getServices()){
      if(s.getType().equals("Cipher")){
         Log.i("cipher_algorithm",s.getAlgorithm());
      }
   }
}
結果

RSA
1.2.840.113549.1.9.16.3.6
BROKENPBEWITHSHAAND3-KEYTRIPLEDES-CBC
PBEWITHSHA1ANDDES
RSA/PKCS1
PBEWITHSHA256AND128BITAES-CBC-BC
1.2.840.113549.3.7
2.5.8.1.1
PBEWITHSHAAND128BITAES-CBC-BC
RSA/2
PBEWITHSHAAND40BITRC2-CBC
PBEWITHMD5AND128BITAES-CBC-OPENSSL
DESEDE
RSA/RAW
PBEWITHSHAAND256BITAES-CBC-BC
PBEWITHMD5AND192BITAES-CBC-OPENSSL
DES
2.16.840.1.101.3.4.1.2
IES
AESWRAP
PBEWITHMD5ANDDES
PBEWITHSHA256AND192BITAES-CBC-BC
AES
2.16.840.1.101.3.4.1.24
1.2.840.113549.1.1.1
2.16.840.1.101.3.4.1.44
2.16.840.1.101.3.4.1.43
BROKENPBEWITHSHA1ANDDES
PBEWITHSHAAND3-KEYTRIPLEDES-CBC
PBEWITHMD5AND256BITAES-CBC-OPENSSL
BROKENPBEWITHMD5ANDDES
2.16.840.1.101.3.4.1.22
2.16.840.1.101.3.4.1.23
2.16.840.1.101.3.4.1.41
2.16.840.1.101.3.4.1.42
PBEWITHMD5ANDRC2
OLDPBEWITHSHAAND3-KEYTRIPLEDES-CBC
RSA/1
1.3.14.3.2.7
PBEWITHSHAAND2-KEYTRIPLEDES-CBC
DESEDEWRAP
RSA/OAEP
2.16.840.1.101.3.4.1.21
RSA/ISO9796-1
2.16.840.1.101.3.4.1.1
PBEWITHSHAAND192BITAES-CBC-BC
BROKENPBEWITHSHAAND2-KEYTRIPLEDES-CBC
BrokenIES
2.16.840.1.101.3.4.1.3
1.2.840.113549.1.1.7
2.16.840.1.101.3.4.1.4
PBEWITHSHA256AND256BITAES-CBC-BC

結論
ほしい暗号化方式は現在のところ導入されていませんでした...

ラベル: