Каков наиболее подходящий способ хранения пользовательских настроек в приложении Android

Я создаю приложение, которое подключается к серверу с использованием имени пользователя/пароля, и я бы хотел включить параметр "Сохранить пароль", чтобы пользователю не приходилось вводить пароль каждый раз, когда приложение запускается.

Я пытался сделать это с помощью общих настроек, но не уверен, что это лучшее решение.

Я был бы признателен за любое предложение о том, как хранить пользовательские значения/настройки в приложении Android.

Ответ 1

В общем, SharedPreferences - ваш лучший выбор для сохранения предпочтений, поэтому в целом я бы рекомендовал этот подход для сохранения приложений и пользовательских настроек.

Единственное, что вызывает беспокойство, это то, что вы экономите. Пароли всегда сложная вещь для хранения, и я буду особенно осторожно хранить их в виде чистого текста. Архитектура Android такова, что ваше приложение SharedPreferences изолировано, чтобы другие приложения не могли получить доступ к значениям, поэтому там есть какая-то безопасность, но физический доступ к телефону может потенциально позволить доступ к значениям.

Если возможно, я бы подумал об изменении сервера на использование согласованного токена для обеспечения доступа, например OAuth. В качестве альтернативы вам может понадобиться построить какой-то криптографический магазин, хотя это нетривиально. По крайней мере, убедитесь, что вы шифруете пароль, прежде чем записывать его на диск.

Ответ 2

Я согласен с Reto и fiXedd. Объективно говоря, не имеет большого смысла вкладывать значительные средства и время в шифрование паролей в SharedPreferences, так как любой злоумышленник, имеющий доступ к вашему файлу настроек, скорее всего также получит доступ к вашему двоичному файлу приложения, и, следовательно, ключи для дешифрования пароль.

Однако, как говорится, существует инициатива публичности, посвященная идентификации мобильных приложений, которые хранят свои пароли в открытом тексте в SharedPreferences и освещают неблагоприятные последствия для этих приложений. Для некоторых примеров см. http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/ и http://viaforensics.com/appwatchdog.

В то время как нам нужно больше внимания уделять безопасности в целом, я бы сказал, что такое внимание к этой конкретной проблеме на самом деле существенно не повышает нашу общую безопасность. Однако восприятие является таким, каким оно есть, вот решение для шифрования данных, которые вы размещаете в 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);
        }
    }

}

Ответ 3

О простейшем способе хранения одного предпочтения в Android-активности - сделать что-то вроде этого:

Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();

Если вы беспокоитесь об их безопасности, вы всегда можете зашифровать пароль перед его сохранением.

Ответ 4

Используя фрагмент, предоставленный Ричардом, вы можете зашифровать пароль перед его сохранением. Однако API-интерфейс предпочтений не обеспечивает простой способ перехвата значения и шифрования - вы можете заблокировать его, сохраняя его через прослушиватель OnPreferenceChange, и теоретически вы можете его модифицировать с помощью функции предпочтенияChangeListener, но это приводит к бесконечному циклу.

Я ранее предлагал добавить "скрытые" предпочтения, чтобы выполнить это. Это определенно не лучший способ. Я собираюсь представить два других варианта, которые я считаю более жизнеспособными.

Во-первых, самый простой, находится в 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; 
   }

Второй способ и то, как я теперь предпочитаю, - создать собственные пользовательские настройки, расширяя EditTextPreference, @Override'ing методы setText() и getText(), чтобы setText() шифровал пароль и getText() возвращает null.

Ответ 5

Я знаю, что это немного некромантии, но вы должны использовать Android AccountManager. Он предназначен для этого сценария. Это немного громоздко, но одна из вещей, которые она делает, является недействительной для локальных учетных данных, если SIM-карта меняется, поэтому, если кто-то проведет ваш телефон и выбрасывает новую SIM-карту, ваши учетные данные не будут скомпрометированы.

Это также дает пользователю быстрый и простой способ доступа (и потенциального удаления) сохраненных учетных данных для любой учетной записи, имеющейся на устройстве, из одного места.

SampleSyncAdapter - пример, который использует учетные данные хранимой учетной записи.

Ответ 6

Хорошо; это было какое-то время, так как ответ был добрым - смешанным, но здесь несколько общих ответов. Я исследовал это как сумасшедший, и было сложно построить хороший ответ

  • Метод MODE_PRIVATE считается в целом безопасным, если вы предполагаете, что пользователь не запустил устройство. Ваши данные хранятся в текстовом виде в части файловой системы, доступ к которой может получить только исходная программа. Это позволяет захватить пароль с другим приложением на корневом устройстве. Опять же, хотите ли вы поддерживать внедренные устройства?

  • AES по-прежнему лучший шифрование, которое вы можете сделать. Не забудьте взглянуть на это, если вы начинаете новую реализацию, если прошло некоторое время с тех пор, как я опубликовал это. Самая большая проблема с этим: "Что делать с ключом шифрования?"

Итак, теперь мы находимся в разделе "Что делать с ключом?". часть. Это трудная часть. Получение ключа оказывается не таким уж плохим. Вы можете использовать функцию деривации ключа, чтобы взять некоторый пароль и сделать его довольно безопасным ключом. Вы сталкиваетесь с такими проблемами, как "сколько проходов вы делаете с PKFDF2?", Но эта другая тема

  • В идеале вы сохраняете ключ AES с устройства. Вы должны найти хороший способ получить ключ с сервера безопасно, надежно и надежно, хотя

  • У вас есть некоторая последовательность входа (даже исходная последовательность входа для удаленного доступа). Вы можете сделать два прогона вашего генератора ключей на одном и том же пароле. Как это работает, вы дважды извлекаете ключ с новой солью и новый безопасный вектор инициализации. Вы сохраняете один из этих сгенерированных паролей на устройстве, и вы используете второй пароль как ключ AES.

При входе в систему вы повторно извлекаете ключ из локального входа и сравниваете его с сохраненным ключом. Как только это будет сделано, вы используете ключ для вывода 2 для AES.

  • Используя подход "в целом безопасный", вы шифруете данные с помощью AES и сохраняете ключ в MODE_PRIVATE. Это рекомендуется в новостной блоге Android. Не невероятно безопасно, но лучше для некоторых людей по сравнению с обычным текстом.

Вы можете сделать много вариаций. Например, вместо полной логической последовательности вы можете сделать быстрый PIN-код (полученный). Быстрый PIN-код может быть не таким безопасным, как полная последовательность входа, но он во много раз более безопасен, чем обычный текст

Ответ 7

Я брошу свою шляпу в кольцо, чтобы поговорить об обеспечении паролей вообще на Android. На Android двоичный код устройства должен считаться скомпрометированным - это то же самое для любого конечного приложения, которое находится под прямым контролем пользователя. Концептуально, хакер мог использовать необходимый доступ к двоичному файлу, чтобы декомпилировать его и исправить ваши зашифрованные пароли и т.д.

В качестве такового есть два предложения, которые я хотел бы выслать там, если безопасность вас беспокоит:

1) Не храните фактический пароль. Храните предоставленный токен доступа и используйте токен доступа и подпись телефона для аутентификации сервера сеанса. Преимущество этого в том, что вы можете сделать токен ограниченным сроком, вы не ставите под угрозу исходный пароль, и у вас есть хорошая подпись, которую вы можете использовать для корреляции с трафиком позже (например, для проверки попыток вторжения и аннулирования токен, что делает его бесполезным).

2) Используйте двухфакторную аутентификацию. Это может быть более раздражающим и навязчивым, но для некоторых ситуаций соблюдения неизбежно.

Ответ 8

Вы также можете проверить эту небольшую библиотеку, содержащую указанные вами функции.

https://github.com/kovmarci86/android-secure-preferences

Это похоже на некоторые из других соображений здесь. Надежда помогает:)

Ответ 9

Этот ответ основан на предлагаемом подходе Марка. Создается пользовательская версия класса 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);
    }
}

Это показывает, как его можно использовать - это файл "items", который управляет отображением настроек. Обратите внимание, что он содержит три обычных вида EditTextPreference и один из пользовательских представлений 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/, хотя с разными значениями для ключа и вектора инициализации.

Ответ 10

Прежде всего, я думаю, что данные пользователя не должны храниться на телефоне, и если необходимо хранить данные где-то на телефоне, они должны быть зашифрованы в личных данных приложений. Безопасность учетных данных пользователей должна быть приоритетом приложения.

Чувствительные данные должны храниться безопасно или вообще не сохраняться. В случае потерянного устройства или заражения вредоносными программами данные, хранящиеся в незащищенном состоянии, могут быть скомпрометированы.

Ответ 11

Это дополнительный ответ для тех, кто прибывает сюда, на основе названия вопроса (как и я), и не нужно заниматься проблемами безопасности, связанными с сохранением паролей.

Как использовать общие настройки

Пользовательские настройки обычно сохраняются локально на Android с помощью SharedPreferences с парой ключ-значение. Вы используете клавишу 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);

Значение по умолчанию используется, если ключ не найден.

Примечания

  • Вместо того, чтобы использовать локальный ключ String в нескольких местах, подобных описанным выше, было бы лучше использовать константу в одном месте. Вы можете использовать что-то вроде этого в верхней части своей настройки:

    final static String PREF_MY_INT_KEY = "myInt";
    
  • Я использовал int в моем примере, но вы также можете использовать putString(), putBoolean(), getString(), getBoolean() и т.д.

  • Подробнее см. документация.
  • Существует несколько способов получения SharedPreferences. См. этот ответ за тем, что нужно искать.

Ответ 12

вам нужно использовать sqlite, security apit для хранения паролей. вот лучший пример, который хранит пароли, - passwordsafe. вот ссылка для источника и объяснения - http://code.google.com/p/android-passwordsafe/

Ответ 13

общие настройки - самый простой способ хранения данных наших приложений. но возможно, что кто-то может очистить данные общих настроек через приложение manager.so, я не думаю, что это абсолютно безопасно для нашего приложения.