回答:
一般に、SharedPreferencesは設定を保存するための最善の方法です。そのため、一般的には、アプリケーションとユーザー設定を保存するためのそのアプローチをお勧めします。
ここでの唯一の関心領域は、あなたが節約しているものです。パスワードは常に保存するのが難しいので、クリアテキストとして保存する場合は特に注意が必要です。Androidアーキテクチャは、他のアプリケーションが値にアクセスできないようにアプリケーションのSharedPreferencesがサンドボックス化されているため、セキュリティが確保されていますが、電話に物理的にアクセスすると、値にアクセスできる可能性があります。
可能であれば、アクセスを提供するためにネゴシエートされたトークンを使用するようにサーバーを変更することを検討します(OAuthなど)。あるいは、ある種の暗号ストアを構築する必要があるかもしれませんが、それは簡単ではありません。少なくとも、ディスクに書き込む前にパスワードを暗号化していることを確認してください。
MODE_PRIVATE
ローカルに保存されたデータを難読化する有効性の観点から、SharedPreferences での使用は、内部ストレージで作成されたファイルで同じことを行うことと同等ですか?
RetoとfiXeddに同意します。客観的に言えば、SharedPreferencesのパスワードの暗号化に多大な時間と労力を費やすことはあまり意味がありません。設定ファイルにアクセスできる攻撃者は、アプリケーションのバイナリにもアクセスできる可能性が高いため、パスワード。
ただし、そうは言っても、SharedPreferencesにパスワードを平文で保存するモバイルアプリケーションを特定し、それらのアプリケーションに不利な光を当てる宣伝活動が行われているようです。いくつかの例については、http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/およびhttp://viaforensics.com/appwatchdogを参照してください。
セキュリティ全般にもっと注意を払う必要がありますが、この1つの特定の問題に対するこの種の注意は、実際には全体的なセキュリティを大幅に向上させるものではないと主張します。ただし、認識はそのままであるため、SharedPreferencesに配置するデータを暗号化するソリューションを次に示します。
これに独自のSharedPreferencesオブジェクトをラップするだけで、読み取り/書き込みデータはすべて自動的に暗号化および復号化されます。例えば。
final SharedPreferences prefs = new ObscuredSharedPreferences(
this, this.getSharedPreferences(MY_PREFS_FILE_NAME, Context.MODE_PRIVATE) );
// eg.
prefs.edit().putString("foo","bar").commit();
prefs.getString("foo", null);
これがクラスのコードです:
/**
* Warning, this gives a false sense of security. If an attacker has enough access to
* acquire your password store, then he almost certainly has enough access to acquire your
* source binary and figure out your encryption key. However, it will prevent casual
* investigators from acquiring passwords, and thereby may prevent undesired negative
* publicity.
*/
public class ObscuredSharedPreferences implements SharedPreferences {
protected static final String UTF8 = "utf-8";
private static final char[] SEKRIT = ... ; // INSERT A RANDOM PASSWORD HERE.
// Don't use anything you wouldn't want to
// get out there if someone decompiled
// your app.
protected SharedPreferences delegate;
protected Context context;
public ObscuredSharedPreferences(Context context, SharedPreferences delegate) {
this.delegate = delegate;
this.context = context;
}
public class Editor implements SharedPreferences.Editor {
protected SharedPreferences.Editor delegate;
public Editor() {
this.delegate = ObscuredSharedPreferences.this.delegate.edit();
}
@Override
public Editor putBoolean(String key, boolean value) {
delegate.putString(key, encrypt(Boolean.toString(value)));
return this;
}
@Override
public Editor putFloat(String key, float value) {
delegate.putString(key, encrypt(Float.toString(value)));
return this;
}
@Override
public Editor putInt(String key, int value) {
delegate.putString(key, encrypt(Integer.toString(value)));
return this;
}
@Override
public Editor putLong(String key, long value) {
delegate.putString(key, encrypt(Long.toString(value)));
return this;
}
@Override
public Editor putString(String key, String value) {
delegate.putString(key, encrypt(value));
return this;
}
@Override
public void apply() {
delegate.apply();
}
@Override
public Editor clear() {
delegate.clear();
return this;
}
@Override
public boolean commit() {
return delegate.commit();
}
@Override
public Editor remove(String s) {
delegate.remove(s);
return this;
}
}
public Editor edit() {
return new Editor();
}
@Override
public Map<String, ?> getAll() {
throw new UnsupportedOperationException(); // left as an exercise to the reader
}
@Override
public boolean getBoolean(String key, boolean defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Boolean.parseBoolean(decrypt(v)) : defValue;
}
@Override
public float getFloat(String key, float defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Float.parseFloat(decrypt(v)) : defValue;
}
@Override
public int getInt(String key, int defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Integer.parseInt(decrypt(v)) : defValue;
}
@Override
public long getLong(String key, long defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Long.parseLong(decrypt(v)) : defValue;
}
@Override
public String getString(String key, String defValue) {
final String v = delegate.getString(key, null);
return v != null ? decrypt(v) : defValue;
}
@Override
public boolean contains(String s) {
return delegate.contains(s);
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
delegate.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
delegate.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
}
protected String encrypt( String value ) {
try {
final byte[] bytes = value!=null ? value.getBytes(UTF8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP),UTF8);
} catch( Exception e ) {
throw new RuntimeException(e);
}
}
protected String decrypt(String value){
try {
final byte[] bytes = value!=null ? Base64.decode(value,Base64.DEFAULT) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(pbeCipher.doFinal(bytes),UTF8);
} catch( Exception e) {
throw new RuntimeException(e);
}
}
}
Androidアクティビティに単一の設定を保存する最も簡単な方法は、次のようなものです。
Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();
これらのセキュリティが心配な場合は、パスワードを保存する前にいつでも暗号化できます。
Richardが提供するスニペットを使用すると、保存する前にパスワードを暗号化できます。ただし、設定APIは値を傍受して暗号化する簡単な方法を提供しません。OnPreferenceChangeリスナーを介して保存されるのをブロックでき、理論的には、preferenceChangeListenerを介して変更できますが、結果として無限ループになります。
私は以前、これを達成するために「隠し」設定を追加することを提案していました。それは間違いなく最良の方法ではありません。私はより実行可能であると考える他の2つのオプションを紹介します。
まず、最も簡単なのは、preferenceChangeListenerです。入力した値を取得して暗号化し、別の設定ファイルに保存できます。
public boolean onPreferenceChange(Preference preference, Object newValue) {
// get our "secure" shared preferences file.
SharedPreferences secure = context.getSharedPreferences(
"SECURE",
Context.MODE_PRIVATE
);
String encryptedText = null;
// encrypt and set the preference.
try {
encryptedText = SimpleCrypto.encrypt(Preferences.SEED,(String)newValue);
Editor editor = secure.getEditor();
editor.putString("encryptedPassword",encryptedText);
editor.commit();
}
catch (Exception e) {
e.printStackTrace();
}
// always return false.
return false;
}
第二の方法、と私は今、好みの方法は、Override'ing @、EditTextPreferenceを拡張し、独自のカスタム設定を作成することですsetText()
し、getText()
そのため、メソッドをsetText()
暗号化し、パスワード、およびgetText()
リターンはnull。
はい; 答えが少し混ざっているので久しぶりですが、ここにいくつかの一般的な答えがあります。私はこれを狂ったように研究しました、そして良い答えを作るのは困難でした
ユーザーがデバイスのルート権限を取得していないと想定する場合、MODE_PRIVATEメソッドは一般的に安全であると見なされます。データは、元のプログラムだけがアクセスできるファイルシステムの一部にプレーンテキストで保存されます。これにより、ルート化されたデバイス上の別のアプリでパスワードを簡単に取得できます。次に、ルート化されたデバイスをサポートしますか?
AESは、あなたができる最高の暗号化です。私がこれを投稿してからしばらく経っていない場合、新しい実装を開始する場合は、これを調べることを忘れないでください。これに関する最大の問題は、「暗号化キーをどうするか」です。
それで、今、私たちは「鍵をどうするか」にいます。部分。これは難しい部分です。キーを取得することはそれほど悪くないことが判明しました。キー導出関数を使用してパスワードを取得し、かなり安全なキーにすることができます。「PKFDF2で何回パスするか」などの問題が発生しますが、それは別のトピックです
理想的には、デバイスからAESキーを保存します。安全に、確実に、そして安全にサーバーからキーを取得するための良い方法を理解する必要があります
ある種のログインシーケンスがあります(リモートアクセスのために行う元のログインシーケンスでさえも)。同じパスワードでキージェネレーターを2回実行できます。これが機能する方法は、新しいソルトと新しい安全な初期化ベクトルを使用してキーを2回導出することです。これらの生成されたパスワードの1つをデバイスに保存し、2番目のパスワードをAESキーとして使用します。
ログインすると、ローカルログインでキーが再取得され、保存されているキーと比較されます。それが完了したら、AESの派生キー#2を使用します。
これらのバリエーションはたくさんあります。たとえば、完全なログインシーケンスの代わりに、クイックPIN(派生)を実行できます。クイックPINは完全なログインシーケンスほど安全ではないかもしれませんが、プレーンテキストよりも何倍も安全です
これは少しネクロマンシーであることはわかっていますが、Android AccountManagerを使用する必要があります。このシナリオ用に作成されています。少し面倒ですが、SIMカードが変更されるとローカル認証情報が無効になるため、誰かが携帯電話をスワイプして新しいSIMをその中に投げ込んでも、認証情報が危険にさらされることはありません。
これにより、ユーザーはデバイス上にあるアカウントの保存された資格情報に、すべて1か所からすばやく簡単にアクセス(および削除)できます。
SampleSyncAdapterは、保存されたアカウント資格情報を利用する例です。
Androidでの一般的なパスワードの保護について話すために、帽子をリングに投げ込みます。Androidでは、デバイスバイナリは危険にさらされていると見なす必要があります。これは、ユーザーが直接制御するすべてのエンドアプリケーションで同じです。概念的には、ハッカーは必要なバイナリへのアクセスを使用してバイナリを逆コンパイルし、暗号化されたパスワードなどをルート化することができます。
そのため、セキュリティが主な懸念事項である場合は、2つの提案を破棄します。
1)実際のパスワードは保存しないでください。許可されたアクセストークンを保存し、アクセストークンと電話の署名を使用して、サーバー側でセッションを認証します。これの利点は、トークンの有効期間を制限できること、元のパスワードを危険にさらすことなく、後でトラフィックに関連付けるために使用できる適切な署名を持っていることです(たとえば、侵入の試みをチェックして、トークンはそれを役に立たなくします)。
2)2要素認証を利用します。これはより面倒で煩わしいかもしれませんが、一部のコンプライアンス状況では避けられません。
言及した機能を含む、この小さなlibもチェックアウトできます。
https://github.com/kovmarci86/android-secure-preferences
これは、他のいくつかのアプローチと似ています。願っています:)
これは、質問のタイトル(私がしたように)に基づいてここに到着した人のための補足的な回答であり、パスワードの保存に関連するセキュリティの問題に対処する必要はありません。
ユーザー設定は通常SharedPreferences
、キーと値のペアを使用してAndroidにローカルに保存されます。String
キーを使用して、関連する値を保存または検索します。
String key = "myInt";
int valueToSave = 10;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, valueToSave).commit();
apply()
代わりにcommit()
を使用して、すぐにではなくバックグラウンドで保存します。
String key = "myInt";
int defaultValue = 0;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
int savedValue = sharedPref.getInt(key, defaultValue);
キーが見つからない場合は、デフォルト値が使用されます。
この回答は、マークが提案したアプローチに基づいています。EditTextPreferenceクラスのカスタムバージョンが作成され、ビューに表示されるプレーンテキストと、プリファレンスストレージに保存されているパスワードの暗号化されたバージョンとの間で相互に変換されます。
このスレッドで回答したほとんどの人が指摘したように、これはあまり安全な手法ではありませんが、セキュリティの程度は、使用する暗号化/復号化コードに部分的に依存します。しかし、それはかなりシンプルで便利であり、ほとんどのカジュアルなスヌーピングを阻止します。
カスタムEditTextPreferenceクラスのコードは次のとおりです。
package com.Merlinia.OutBack_Client;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.util.Base64;
import com.Merlinia.MEncryption_Main.MEncryptionUserPassword;
/**
* This class extends the EditTextPreference view, providing encryption and decryption services for
* OutBack user passwords. The passwords in the preferences store are first encrypted using the
* MEncryption classes and then converted to string using Base64 since the preferences store can not
* store byte arrays.
*
* This is largely copied from this article, except for the encryption/decryption parts:
* https://groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6M
*/
public class EditPasswordPreference extends EditTextPreference {
// Constructor - needed despite what compiler says, otherwise app crashes
public EditPasswordPreference(Context context) {
super(context);
}
// Constructor - needed despite what compiler says, otherwise app crashes
public EditPasswordPreference(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
// Constructor - needed despite what compiler says, otherwise app crashes
public EditPasswordPreference(Context context, AttributeSet attributeSet, int defaultStyle) {
super(context, attributeSet, defaultStyle);
}
/**
* Override the method that gets a preference from the preferences storage, for display by the
* EditText view. This gets the base64 password, converts it to a byte array, and then decrypts
* it so it can be displayed in plain text.
* @return OutBack user password in plain text
*/
@Override
public String getText() {
String decryptedPassword;
try {
decryptedPassword = MEncryptionUserPassword.aesDecrypt(
Base64.decode(getSharedPreferences().getString(getKey(), ""), Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
decryptedPassword = "";
}
return decryptedPassword;
}
/**
* Override the method that gets a text string from the EditText view and stores the value in
* the preferences storage. This encrypts the password into a byte array and then encodes that
* in base64 format.
* @param passwordText OutBack user password in plain text
*/
@Override
public void setText(String passwordText) {
byte[] encryptedPassword;
try {
encryptedPassword = MEncryptionUserPassword.aesEncrypt(passwordText);
} catch (Exception e) {
e.printStackTrace();
encryptedPassword = new byte[0];
}
getSharedPreferences().edit().putString(getKey(),
Base64.encodeToString(encryptedPassword, Base64.DEFAULT))
.commit();
}
@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
if (restoreValue)
getEditText().setText(getText());
else
super.onSetInitialValue(restoreValue, defaultValue);
}
}
これは、それをどのように使用できるかを示しています。これは、設定の表示を制御する「アイテム」ファイルです。3つの通常のEditTextPreferenceビューと1つのカスタムEditPasswordPreferenceビューが含まれていることに注意してください。
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="@string/useraccountname_key"
android:title="@string/useraccountname_title"
android:summary="@string/useraccountname_summary"
android:defaultValue="@string/useraccountname_default"
/>
<com.Merlinia.OutBack_Client.EditPasswordPreference
android:key="@string/useraccountpassword_key"
android:title="@string/useraccountpassword_title"
android:summary="@string/useraccountpassword_summary"
android:defaultValue="@string/useraccountpassword_default"
/>
<EditTextPreference
android:key="@string/outbackserverip_key"
android:title="@string/outbackserverip_title"
android:summary="@string/outbackserverip_summary"
android:defaultValue="@string/outbackserverip_default"
/>
<EditTextPreference
android:key="@string/outbackserverport_key"
android:title="@string/outbackserverport_title"
android:summary="@string/outbackserverport_summary"
android:defaultValue="@string/outbackserverport_default"
/>
</PreferenceScreen>
実際の暗号化/復号化については、読者の練習問題として残しておきます。私は現在、この記事http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/に基づいていくつかのコードを使用していますが、値は異なりますキーと初期化ベクトル。
Android KeyStoreを使用して、RSBをECBモードで使用してパスワードを暗号化し、SharedPreferencesに保存します。
パスワードを戻したい場合は、SharedPreferencesから暗号化されたパスワードを読み取り、KeyStoreを使用してそれを復号化します。
この方法を使用して、Androidによって安全に保存および管理される公開鍵/秘密鍵のペアを生成します。
これを行う方法に関するリンクは次のとおりです。AndroidKeyStoreチュートリアル
共有設定は、アプリケーションデータを保存する最も簡単な方法です。しかし、誰でもアプリケーションマネージャを介して共有設定データをクリアできる可能性があります。そのため、アプリケーションにとって完全に安全だとは思いません。
パスワードを保存するには、sqlite、セキュリティピットを使用する必要があります。これがパスワードを保存する最良の例です-passwordsafe。ここにソースと説明のリンクがあり ます-http://code.google.com/p/android-passwordsafe/