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

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

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

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

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

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

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

Я согласен с 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); 

Вот код для classа:

 /** * 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 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(); 

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

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

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

Во-первых, самый простой, в предпочтенииChangeListener, вы можете захватить введенное значение, зашифровать его и затем сохранить в альтернативном файле предпочтений:

  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() пароль, а getText() возвращал ноль.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пользовательские настройки обычно сохраняются локально на 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. См. Этот ответ за то, что нужно посмотреть.

Этот ответ основан на предлагаемом подходом Марка. Создается пользовательская версия classа EditTextPreference, которая преобразует назад и вперед между открытым текстом, видимым в представлении, и зашифрованной версией пароля, хранящейся в хранилище предпочтений.

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

Вот код для настраиваемого classа 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.

       

Что касается фактического шифрования / дешифрования, то это остается как упражнение для читателя. В настоящее время я использую код, основанный на этой статье http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/ , хотя с разными значениями для ключа и вектора инициализации.

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

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

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

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

Interesting Posts
Давайте будем гением компьютера.