Preferenceでパスワードを保存

2,682 views
Skip to first unread message

ドラ焼き

unread,
Dec 26, 2011, 6:49:40 AM12/26/11
to android-g...@googlegroups.com
2ヶ月ほどROMっていました、初投稿のドラ焼きです。
現在外部のネットサービスと連携するアプリを作っていて、ログインするためにメールアドレスとパスワードを保存する必要があります。
そこでふと疑問に思ったのですが、マーケットにはたくさんのSNSと連携するアプリがありますが、パスワードなどは暗号化してPreferenceに保存しているのでしょうか?
最近Androidでもウィルスが流行っているので、少し心配になり、質問させていただきました。

暗号化するとしたら、EdittextPreferenceをExtendしたClassを作り、保存時に暗号化・編集時に複合化ということでいいでしょうか?
--
HP:http://dorasw.rosx.net
Twitter:@_dorayaki_

typex20

unread,
Dec 26, 2011, 9:08:16 AM12/26/11
to 日本Androidの会
ドラ焼きさん。

突然消失するかもしれないブログのなかのひと、なかのきえたです。こんにちは。

ちょっと宣伝になってしまいますが、インプレスさんからタオソフトウェアさん渾身のAndroidアプリ開発者向けのセキュリティガイド本が2012年
1月6日に発売予定となっています。

タオソフトウェアの谷口さんと言えば、先日の定例会でAndroidアプリ開発者向けのセキュリティについて講演されていた方です。

認証情報など大切なデータを端末内に保存しなければならない時は、Androidに限らず適切に暗号化するなどして端末内に安全に保管する必要がありま
す。他にもセキュリティ的にいろいろ考慮しなければならないことがありますが、本書をご覧頂くと一通り学習することができます。

付録にチェックシートもついていますので、是非とも一度手にとって頂けると幸いです。(^_^)

○Android Security 安全なアプリケーションを作成するために
 http://www.impressjapan.jp/books/3134

ドラ焼き

unread,
Dec 26, 2011, 11:04:50 PM12/26/11
to 日本Androidの会
typexさん

そのような本が出るのですね、今度読んでみたいと思います。
やはり暗号化しなければいけませんね・・・(汗)
Blowfishを使って端末ごとに任意のキーを作って暗号化したいと思います。

typex20

unread,
Dec 27, 2011, 12:08:38 AM12/27/11
to 日本Androidの会
ドラ焼きさん。

なかのきえたです。こんにちは。

> そのような本が出るのですね、今度読んでみたいと思います。

是非、オススメします。(^_^)ノ

> やはり暗号化しなければいけませんね・・・(汗)
> Blowfishを使って端末ごとに任意のキーを作って暗号化したいと思います。

上記については、本書の第10章 暗号化手法 にまとめられていますので、導入は難しくないと思います。

暗号化方式の選択も重要なのですが、本書の第9章 アプリケーションの保護 もアプリケーション開発者にとって重要です。

暗号化する際に使用する鍵がアプリケーションから解析されて盗まれてしまうと意味がなくなってしまうためです。

Androidのアプリケーションは主にJavaで開発され、DEX形式に変換されて、実行されます。

(もちろんJNIを使ってネイティブライブラリを使用したり、Android NDKを使用して純粋なネイティブプログラムである
NativeActivityも作成することもできます。)

Androidのアプリケーションを含め一般的にJavaプログラムは、リバースエンジニアリングに対して無力なので、何も対策をしなければ、アプリケ
ーションにソースコードをつけて配布しているのと同じようなものとよく言われます。こちらの事情は以下の書籍に詳しく解説されています。

デコンパイリングJava――逆解析技術とコードの難読化
http://www.oreilly.co.jp/books/9784873114491/

プロの方でないのであれば、そこまで頑張る必要もないと思いますが、もし、今後、お仕事でAndroidのアプリケーションを開発されることがあるので
したら、上記の本などもご一読されることをオススメします。

Java難読化ツールのProGuardがAndroid SDKで採用されていますが、ProGuardによる難読化の効果は残念ながらあまり期待で
きません。私のようなレベルの人間でも十分解析できてしまいます。ですが、カジュアルハックを阻害するくらいの効果は期待できるかもしれないので、是
非、試してみて下さい。

ちなみに、非常に高額で法人向けの製品ではありますが、以下のような製品もあります。ご参考まで。

<参考>

○DashO
 http://www.agtech.co.jp/products/preemptive/dasho/
 ※Java難読化ツール。Androidにも正式対応済み。

○CrackProof for Android
 http://www.crackproofand.biz/
 ※Androidのアプリケーションのクラッキング(アプリケーションの不正な解析・改ざんなど)防止ソフト。

ではでは。

yasuyuki

unread,
Dec 27, 2011, 2:14:42 AM12/27/11
to 日本Androidの会
えんどうです。

ログイン時にサーバーからトークンを発行し、そのトークンをAccountManagerで保存するようにすれば端末にパスワードを保存しなくても良い
のではないでしょうか。

詳しくは、この本の第15章、

Android in Action, Third Edition
http://www.manning.com/ableson3/

またはSDK付属のSampleSyncAdapterのソースを見てください。

On 12月26日, 午後8:49, ドラ焼き <dorayaki.pikac...@gmail.com> wrote:

ドラ焼き

unread,
Dec 27, 2011, 4:06:40 AM12/27/11
to 日本Androidの会
えんどうさん

AccountManagerを使うという手もあるのですね。
今回はBlowfishで暗号化する形で行こうと思います。

そこで以下のようなソースを書いたのですが、エラーとなってしまいます・・・

Preference.xml:
<jp.ddo.dorasoft.dokushometer.connector.PasswordPreference
android:title="@string/pref_pass" android:key="dm_pass"
android:dependency="browser" android:password="true"></
jp.ddo.dorasoft.dokushometer.connector.PasswordPreference>

PasswordPreference.java:
package jp.ddo.dorasoft.dokushometer.connector;

import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.util.Log;

public class PasswordPreference extends EditTextPreference {
public PasswordPreference(Context context) {
super(context);
// TODO 自動生成されたコンストラクター・スタブ
}

public PasswordPreference(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO 自動生成されたコンストラクター・スタブ
}

public PasswordPreference(Context context, AttributeSet attrs, int
defStyle) {
super(context, attrs, defStyle);
// TODO 自動生成されたコンストラクター・スタブ
}

@Override
protected void onPrepareDialogBuilder(Builder builder) {
super.onPrepareDialogBuilder(builder);

// get value from xml file
String value = getText();

// convert value
value = Common.decrypt(value, (Activity) this.getContext());

// display converted value
this.getEditText().setText(value);
}

@Override
public void onClick(DialogInterface dialog, int which) {
// re-convert value and save to xml
String value = this.getEditText().getText().toString();
value = Common.Crypt(value, (Activity) this.getContext());
setText(value);
}
}

Common.java:
public class Common {
/** アルゴリズム */
private static final String ALGORITHM = "Blowfish";
public static String Crypt(String str, Activity a) {
String key = randamKey(a);
Log.d("DMeter", "CryptKey:" + key);

// 暗号化
byte[] encryptValue = encrypt(key, str.getBytes());
Log.d("DMeter", "str=" + str + " encryptValue=" + new
String(encryptValue));
String cryptstr = new String(encryptValue);

return cryptstr;
}

public static String decrypt(String encrypted, Activity a) {
javax.crypto.spec.SecretKeySpec keySpec = new
javax.crypto.spec.SecretKeySpec(randamKey(a).getBytes(), ALGORITHM);
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return new String(cipher.doFinal(encrypted.getBytes()));
} catch (Exception e) {
Log.d("DMeter", "複合化でエラーが発生しました。");
throw new RuntimeException(e);
}
}

private static byte[] encrypt(String key, byte[] value) {
javax.crypto.spec.SecretKeySpec keySpec = new
javax.crypto.spec.SecretKeySpec(key.getBytes(), ALGORITHM);
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);←ここでエラー
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(value);
} catch (Exception e) {
Log.d("DMeter", "暗号化でエラーが発生しました。");
throw new RuntimeException(e);
}
}

public static String randamKey(Activity a) {
String key = Settings.Secure.getString(a.getContentResolver(),
Settings.System.ANDROID_ID);;
key += "mtdpoirkaaycaykuiu794ugishiank710hjk";

return key;
}
}

12-27 08:46:54.547: ERROR/AndroidRuntime(320):
java.lang.RuntimeException: java.security.NoSuchAlgorithmException:
Cipher Blowfish implementation not found
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
jp.ddo.dorasoft.dokushometer.connector.Common.decrypt(Common.java:124)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
jp.ddo.dorasoft.dokushometer.connector.PasswordPreference.onPrepareDialogBuilder(PasswordPreference.java:
36)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.preference.DialogPreference.showDialog(DialogPreference.java:
291)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.preference.DialogPreference.onClick(DialogPreference.java:262)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.preference.Preference.performClick(Preference.java:811)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.preference.PreferenceScreen.onItemClick(PreferenceScreen.java:
190)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.widget.AdapterView.performItemClick(AdapterView.java:284)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.widget.ListView.performItemClick(ListView.java:3246)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.widget.AbsListView$PerformClick.run(AbsListView.java:1635)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.os.Handler.handleCallback(Handler.java:587)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.os.Handler.dispatchMessage(Handler.java:92)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.os.Looper.loop(Looper.java:123)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
android.app.ActivityThread.main(ActivityThread.java:4203)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
java.lang.reflect.Method.invokeNative(Native Method)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
java.lang.reflect.Method.invoke(Method.java:521)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
com.android.internal.os.ZygoteInit
$MethodAndArgsCaller.run(ZygoteInit.java:791)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
dalvik.system.NativeStart.main(Native Method)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): Caused by:
java.security.NoSuchAlgorithmException: Cipher Blowfish implementation
not found
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
org.apache.harmony.security.fortress.Engine.getInstance(Engine.java:
104)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
javax.crypto.Cipher.getCipher(Cipher.java:288)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
javax.crypto.Cipher.getInstance(Cipher.java:196)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): at
jp.ddo.dorasoft.dokushometer.connector.Common.decrypt(Common.java:119)
12-27 08:46:54.547: ERROR/AndroidRuntime(320): ... 17 more

どうやらアルゴリズムが正しくないようです。
コンパイルはできますが、どうしてうまくいかないのかよくわかりません。。。

ご教授お願いします。

Nikolay Elenkov

unread,
Dec 27, 2011, 4:41:23 AM12/27/11
to android-g...@googlegroups.com
2011/12/27 ドラ焼き <dorayaki...@gmail.com>:

> そこで以下のようなソースを書いたのですが、エラーとなってしまいます・・・

>    /** アルゴリズム */
>    private static final String ALGORITHM = "Blowfish";

少なくとも Gingerbread のBC プロバイダでは、"BLOWFISH" (大文字)
と呼んでいます。


>
>        public static String randamKey(Activity a) {
>                String key = Settings.Secure.getString(a.getContentResolver(),
> Settings.System.ANDROID_ID);;
>        key += "mtdpoirkaaycaykuiu794ugishiank710hjk";
>
>                return key;
>        }
> }

ANDROID_IDはだれでも見られるので、そのまま鍵とし使わないほうが
いいでしょう。ランダムっぽい文字列を連結しても、よりセキュアにならないので、
例えば、パスフレーズにして、PBEで鍵をderiveしたほうがいいでしょう。

ドラ焼き

unread,
Dec 27, 2011, 7:26:53 AM12/27/11
to 日本Androidの会
Nikolay Elenkovさん

> 少なくとも Gingerbread のBC プロバイダでは、"BLOWFISH" (大文字)
> と呼んでいます。
大文字で書いてみます。

> ANDROID_IDはだれでも見られるので、そのまま鍵とし使わないほうが
> いいでしょう。ランダムっぽい文字列を連結しても、よりセキュアにならないので、
そうですよね。。。思いつかなくて苦肉の策でした

> 例えば、パスフレーズにして、PBEで鍵をderiveしたほうがいいでしょう。
ここの部分の意味がよくわからないので、もう少し詳しく教えていただけないでしょうか?

ドラ焼き

unread,
Dec 27, 2011, 7:29:24 AM12/27/11
to 日本Androidの会
自己レスです。
このサイトのようなことを指すのでしょうか?
> 例えば、パスフレーズにして、PBEで鍵をderiveしたほうがいいでしょう。
http://kobadroid.web.fc2.com/androidfilecrypt.html

Nikolay Elenkov

unread,
Dec 27, 2011, 8:27:00 AM12/27/11
to android-g...@googlegroups.com
2011/12/27 ドラ焼き <dorayaki...@gmail.com>:

はい、そうです。ただし、IVとsaltは、暗号化する度にランダムに
生成しないといけません。じゃないと、例えば、同じ文字列は
常に同じ暗号文になります。そうすると、たとえ復号しなくても、
二人のユーザは同じパスワードを使っているのが簡単に分かります。
ちなみに、2.2 (Froyo)には、Blowfishはありませんので、もうちょっと
一般的にサポートされているAESとかを使った方がいいでしょう。

また、ANDROID_IDですが、端末のバージョンによって、
なかったり、違う端末でも同じ値が設定されたりすることが
あります。さらに、端末を初期化すると、再生成されますので、
例えばTitanium Backupのようなアプリでデータを復元
した場合に、復号化できない状態になります。(レアケースですが)

ANDROID_ID の話は以下のブログにあります:

http://android-developers.blogspot.com/2011/03/identifying-app-installations.html

あまり答えにはなっていないが、鍵管理ってむずかしいですね。

sakamoto toshiyuki

unread,
Dec 28, 2011, 12:02:46 AM12/28/11
to android-g...@googlegroups.com
坂本といいます。こんにちは。

> あまり答えにはなっていないが、鍵管理ってむずかしいですね。

そのためにOAuthのような技術があるので・・・

OAuthに対応しているサービスを使うならばOAuthを使用し、自前でサービスを作る場合でも、認証周りは自前では作らずOpenIDを使用するなどの方法を検討してはいかがでしょうか。

それでは


2011年12月27日22:27 Nikolay Elenkov <nikolay...@gmail.com>:

> --
> このメールは Google グループのグループ「日本Androidの会」の登録者に送られています。
> このグループに投稿するには、android-g...@googlegroups.com にメールを送信してください。
> このグループから退会するには、android-group-j...@googlegroups.com にメールを送信してください。
> 詳細については、http://groups.google.com/group/android-group-japan?hl=ja からこのグループにアクセスしてください。
>

ドラ焼き

unread,
Dec 28, 2011, 8:02:01 AM12/28/11
to 日本Androidの会
Nikolay Elenkovさん、坂本さん
素早い返信本当にありがとうございます。
今後の勉強になりました。

今回は自分が作ったサービスでもなく、OAuthも使えないのでAESで暗号化して保存する方法を採用しました。
おかげで無事アプリが完成できそうです。

また解決できないことがあったら、質問させていただくかもしれません。
その時はまたよろしくお願いします。

--
ドラ焼き
Reply all
Reply to author
Forward
0 new messages