Jaki jest najbardziej odpowiedni sposób przechowywania ustawień użytkownika w aplikacji na Androida

308

Tworzę aplikację, która łączy się z serwerem za pomocą nazwy użytkownika / hasła i chciałbym włączyć opcję „Zapisz hasło”, aby użytkownik nie musiał wpisywać hasła przy każdym uruchomieniu aplikacji.

Próbowałem to zrobić z Preferencjami wspólnymi, ale nie jestem pewien, czy to najlepsze rozwiązanie.

Byłbym wdzięczny za wszelkie sugestie dotyczące sposobu przechowywania wartości / ustawień użytkownika w aplikacji na Androida.

Niko Gamulin
źródło

Odpowiedzi:

233

Ogólnie SharedPreferences są najlepszym rozwiązaniem do przechowywania preferencji, więc ogólnie polecam takie podejście do zapisywania ustawień aplikacji i użytkownika.

Jedynym przedmiotem zainteresowania jest to, co oszczędzasz. Hasła są zawsze trudne do przechowywania i szczególnie ostrożnie przechowuję je jako czysty tekst. Architektura Androida polega na tym, że współdzielone preferencje aplikacji są w piaskownicy, aby uniemożliwić innym aplikacjom dostęp do wartości, co zapewnia pewne bezpieczeństwo, ale fizyczny dostęp do telefonu może potencjalnie umożliwić dostęp do tych wartości.

Jeśli to możliwe, rozważę zmodyfikowanie serwera, aby używał wynegocjowanego tokena do zapewniania dostępu, coś w rodzaju OAuth . Alternatywnie może być konieczne zbudowanie jakiegoś sklepu kryptograficznego, choć nie jest to trywialne. Przynajmniej upewnij się, że szyfrujesz hasło przed zapisaniem go na dysku.

Reto Meier
źródło
3
Czy mógłbyś wyjaśnić, co rozumiesz przez piaskownicę?
Abhijit
14
program w trybie piaskownicy to każda aplikacja, której proces i informacje (takie jak te wspólne preferencje) pozostają ukryte przed resztą aplikacji. Aplikacja na Androida działająca w pakiecie nie ma bezpośredniego dostępu do niczego w innym pakiecie. Dlatego wnioski w tym samym opakowaniu (które zawsze są twoje) może uzyskać dostęp do informacji spośród innych
Korcholis
@Reto Meier moim wymogiem jest ochrona publicznie dostępnych usług internetowych, ponieważ używam tokena, czy przechowywanie go we wspólnych preferencjach jest bezpieczne? Mam odbiornik rozgłaszania rozruchu w mojej aplikacji, który usunie wszystkie dane wspólnych preferencji, jeśli znajdzie urządzenie jako zrootowane. Czy to wystarczy, aby chronić mój token.
pyus13
1
Zgodnie z Android-developers.blogspot.com/2013/02 /... Poświadczenia użytkownika powinny być przechowywane z flagą MODE_PRIVATE ustawioną i przechowywaną w pamięci wewnętrznej (z tymi samymi zastrzeżeniami dotyczącymi przechowywania dowolnego rodzaju hasła, ostatecznie ostatecznie otwartego na atak). To powiedziawszy, czy korzystanie MODE_PRIVATEz SharedPreferences jest równoważne robieniu tego samego z plikiem utworzonym w pamięci wewnętrznej, pod względem skuteczności zaciemniania lokalnie przechowywanych danych?
qix 18.10.13
6
Nie przechowuj hasła we wspólnych preferencjach. Jeśli użytkownik kiedykolwiek zgubi telefon, utracił hasło. To będzie czytane. Jeśli użyli tego hasła w innym miejscu, każde miejsce, w którym go użyli, jest zagrożone. Ponadto na stałe utraciłeś to konto, ponieważ za pomocą hasła mogą one zmienić Twoje hasło. Prawidłowym sposobem na to jest jednorazowe przesłanie hasła do serwera i odesłanie tokena logowania. Zapisz to we wspólnych preferencjach i wyślij je przy każdym żądaniu. Jeśli ten token zostanie naruszony, nic więcej nie zostanie utracone.
Gabe Sechan
211

Zgadzam się z Reto i fiXedd. Obiektywnie rzecz biorąc, nie ma sensu inwestowanie znacznego czasu i wysiłku w szyfrowanie haseł we współdzielonych preferencjach, ponieważ osoba atakująca, która ma dostęp do pliku preferencji, prawdopodobnie również ma dostęp do pliku binarnego aplikacji, a zatem kluczy do odszyfrowania hasło.

Niemniej jednak wydaje się, że istnieje inicjatywa reklamowa polegająca na identyfikacji aplikacji mobilnych, które przechowują hasła w postaci jawnego tekstu w SharedPreferences i rzucają niekorzystne światło na te aplikacje. Zobacz przykłady na http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/ i http://viaforensics.com/appwatchdog .

Chociaż potrzebujemy więcej uwagi poświęconej bezpieczeństwu w ogólności, argumentowałbym, że tego rodzaju uwaga w tej konkretnej sprawie w rzeczywistości nie zwiększa znacząco naszego ogólnego bezpieczeństwa. Jednak postrzeganie jest takie, jakie jest, oto rozwiązanie do szyfrowania danych umieszczanych w SharedPreferences.

Po prostu zawiń w nim swój własny obiekt SharedPreferences, a wszystkie dane, które odczytasz / zapiszesz, zostaną automatycznie zaszyfrowane i odszyfrowane. na przykład.

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);

Oto kod klasy:

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

}
emmby
źródło
3
FYI Base64 jest dostępny w wersji API 8 (2.2) i nowszych. Możesz użyć iharder.sourceforge.net/current/java/base64 lub czegoś innego dla wcześniejszych systemów operacyjnych.
emmby
34
Tak, napisałem to. Zachęcamy do korzystania, bez konieczności
podawania źródła
8
Zgadzam się z Tobą. Ale jeśli hasło jest używane tylko na serwerze, dlaczego nie użyć szyfrowania klucza publicznego / prywatnego? Klucz publiczny na kliencie podczas zapisywania hasła. Klient nigdy nie będzie musiał ponownie czytać hasła w postaci zwykłego tekstu, prawda? Serwer może następnie odszyfrować go za pomocą klucza prywatnego. Więc nawet jeśli ktoś przejdzie przez kod źródłowy aplikacji, nie może uzyskać hasła, z wyjątkiem tego, że włamuje się na serwer i otrzymuje klucz prywatny.
Patrick Boos,
4
Dodałem kilka funkcji do tego kodu i umieściłem go na github pod adresem github.com/RightHandedMonkey/WorxForUs_Library/blob/master/src/… . Teraz obsługuje migrację preferencji niezaszyfrowanych do zaszyfrowanych. Generuje również klucz w czasie wykonywania, więc dekompilacja aplikacji nie zwalnia klucza.
RightHandedMonkey
3
Późne dodanie, ale komentarz @PatrickBoos to świetny pomysł. Jednym z problemów jest jednak to, że nawet jeśli zaszyfrowałeś hasło, osoba atakująca, która ukradła ten szyfr, nadal będzie mogła zalogować się na twoje serwery, ponieważ twoje serwery dokonują deszyfrowania. Jednym z dodatków do tego podejścia jest szyfrowanie hasła wraz ze znacznikiem czasu. W ten sposób możesz na przykład zezwolić na stosowanie tylko haseł zapisanych w niedawnej przeszłości (np. Dodanie daty ważności do „tokena”), a nawet wymaganie od niektórych użytkowników posiadania znacznika czasu od określonej daty (pozwólmy „cofnąć” stare „tokeny”).
adevine
29

Najprostszym sposobem na zapisanie jednej preferencji w Aktywności na Androida jest zrobienie czegoś takiego:

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

Jeśli martwisz się o ich bezpieczeństwo, zawsze możesz zaszyfrować hasło przed jego zapisaniem.

Jeremy Logan
źródło
9
Nie mogę zgodzić się z tobą bardziej na temat tego uproszczonego podejścia; jednak zawsze powinieneś martwić się o bezpieczeństwo przechowywanych haseł? W zależności od aplikacji masz potencjalne zobowiązania za skradzione dane osobowe. Wskazując to każdemu, kto próbuje przechowywać rzeczywiste hasła do takich rzeczy jak konta bankowe lub coś równie ważnego. Nadal jednak głosuję.
While-E
1
Gdzie będziesz przechowywać klucz, w którym przechowywane jest hasło? Jeśli udostępnione preferencje są dostępne dla innych użytkowników, kluczem jest również.
OrhanC1,
@ OrhanC1 dostałeś odpowiedź.?
eRaisedToX,
10

Korzystając z fragmentu udostępnionego przez Richarda, możesz zaszyfrować hasło przed jego zapisaniem. Interfejs API preferencji nie zapewnia jednak łatwego sposobu przechwytywania i szyfrowania wartości - możesz zablokować zapisywanie go za pomocą detektora OnPreferenceChange, i teoretycznie możesz go zmodyfikować za pomocą preferenceChangeListener, ale to powoduje nieskończoną pętlę.

Wcześniej zasugerowałem dodanie „ukrytej” preferencji, aby to osiągnąć. To zdecydowanie nie jest najlepszy sposób. Przedstawię dwie inne opcje, które uważam za bardziej opłacalne.

Po pierwsze, najprostszy, znajduje się w preferenceChangeListener, możesz pobrać wprowadzoną wartość, zaszyfrować ją, a następnie zapisać w alternatywnym pliku preferencji:

  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; 
   }

Drugi sposób, i tak, jak wolę teraz, to utworzenie własnych preferencji, rozszerzenie EditTextPreference, @ Zastąpienie metod setText()i getText(), aby setText()szyfrować hasło i getText()zwracać wartość null.

znak
źródło
Wiem, że jest to dość stare, ale czy mógłbyś opublikować kod niestandardowej wersji EditTextPreference?
RenniePet
Nieważne, znalazłem użyteczną próbkę tutaj groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6M i już działa. Dziękujemy za zasugerowanie tego podejścia.
RenniePet,
6

W porządku; minęło trochę czasu, odkąd odpowiedź jest trochę mieszana, ale oto kilka typowych odpowiedzi. Badałem to jak szalone i ciężko było znaleźć dobrą odpowiedź

  1. Metoda MODE_PRIVATE jest ogólnie uważana za bezpieczną, jeśli założymy, że użytkownik nie zrootował urządzenia. Twoje dane są przechowywane jako zwykły tekst w części systemu plików, do której dostęp ma tylko oryginalny program. Ułatwia to chwytanie hasła za pomocą innej aplikacji na zrootowanym urządzeniu. Z drugiej strony, czy chcesz obsługiwać zrootowane urządzenia?

  2. AES to wciąż najlepsze szyfrowanie, jakie możesz zrobić. Pamiętaj, aby to sprawdzić, jeśli zaczynasz nową implementację, jeśli minęło trochę czasu, odkąd to opublikowałem. Największym problemem jest „Co zrobić z kluczem szyfrującym?”

Teraz jesteśmy w temacie „Co zrobić z kluczem?” część. To jest najtrudniejsza część. Zdobycie klucza okazuje się nie takie złe. Możesz użyć funkcji wyprowadzania klucza, aby wziąć hasło i uczynić z niego całkiem bezpieczny klucz. Wchodzisz w takie problemy, jak „ile przebiegów robisz z PKFDF2?”, Ale to już inny temat

  1. Najlepiej jest przechowywać klucz AES poza urządzeniem. Musisz jednak znaleźć dobry sposób na odzyskanie klucza z serwera w sposób bezpieczny, niezawodny i bezpieczny

  2. Masz jakąś sekwencję logowania (nawet oryginalną sekwencję logowania dla dostępu zdalnego). Możesz wykonać dwa uruchomienia generatora kluczy na tym samym haśle. Działa to tak, że klucz uzyskuje się dwa razy z nową solą i nowym bezpiecznym wektorem inicjującym. Przechowujesz jedno z wygenerowanych haseł na urządzeniu i używasz drugiego hasła jako klucza AES.

Po zalogowaniu wyprowadzasz klucz z lokalnego loginu i porównujesz go z zapisanym kluczem. Gdy to zrobisz, użyjesz klucza # 2 dla AES.

  1. Stosując podejście „ogólnie bezpieczne”, szyfrujesz dane za pomocą AES i przechowujesz klucz w MODE_PRIVATE. Jest to zalecane w najnowszym poście na Androida. Nie jest niewiarygodnie bezpieczny, ale dla niektórych osób lepszy niż zwykły tekst

Możesz zrobić wiele ich odmian. Na przykład zamiast pełnej sekwencji logowania możesz zrobić szybki kod PIN (wyprowadzony). Szybki kod PIN może nie być tak bezpieczny jak pełna sekwencja logowania, ale jest wielokrotnie bezpieczniejszy niż zwykły tekst

Joe Plante
źródło
5

Wiem, że to trochę nekromancja, ale powinieneś użyć Android AccountManager . Jest specjalnie zaprojektowany dla tego scenariusza. Jest to trochę uciążliwe, ale jedną z rzeczy, które robi, jest unieważnianie lokalnych poświadczeń, jeśli karta SIM się zmieni, więc jeśli ktoś przesunie twój telefon i wrzuci nową kartę SIM, twoje poświadczenia nie zostaną naruszone.

Daje to również użytkownikowi szybki i łatwy sposób na dostęp (i potencjalne usunięcie) przechowywanych poświadczeń dla dowolnego konta, które mają na urządzeniu, wszystko z jednego miejsca.

SampleSyncAdapter to przykład, który wykorzystuje zapisane poświadczenia konta.

Jon O
źródło
1
Pamiętaj, że korzystanie z AccountManager nie jest bezpieczniejsze niż jakakolwiek inna metoda opisana powyżej! developer.android.com/training/id-auth/…
Sander Versluys
1
Przypadek użycia AccountManager dotyczy sytuacji, gdy konto musi być współużytkowane przez różne aplikacje i aplikacje różnych autorów. Przechowywanie hasła i przekazywanie go dowolnej aplikacji, która go zażądała, nie byłoby właściwe. Jeśli użycie użytkownika / hasła dotyczy tylko jednej aplikacji, nie używaj AccountManager.
dolmen
1
@dolmen, to nie do końca poprawne. Menedżer konta nie przekaże hasła do konta żadnej aplikacji, której identyfikator UID nie zgadza się z identyfikatorem użytkownika. Imię tak; token uwierzytelniania, tak; hasło, nie. Jeśli spróbujesz, wygeneruje wyjątek SecurityException. A przypadek użycia jest znacznie szerszy. developer.android.com/training/id-auth/identify.html
Jon O
5

Wrzucę kapelusz na ring, żeby porozmawiać o zabezpieczeniu haseł w ogóle na Androidzie. Na Androidzie binarne urządzenie należy uznać za zagrożone - to samo dotyczy każdej aplikacji końcowej, która jest pod bezpośrednią kontrolą użytkownika. Pod względem koncepcyjnym haker mógłby użyć niezbędnego dostępu do pliku binarnego, aby go zdekompilować i wykorzenić zaszyfrowane hasła itp.

Jako takie są dwie sugestie, które chciałbym tam przedstawić, jeśli bezpieczeństwo jest dla Ciebie najważniejsze:

1) Nie przechowuj aktualnego hasła. Zapisz przyznany token dostępu i użyj tokena dostępu oraz podpisu telefonu do uwierzytelnienia sesji po stronie serwera. Zaletą tego jest to, że możesz sprawić, by token miał ograniczony czas trwania, nie naruszasz oryginalnego hasła i masz dobrą sygnaturę, której możesz użyć do późniejszej korelacji z ruchem (na przykład w celu sprawdzenia prób włamania i unieważnienia token czyniący go bezużytecznym).

2) Wykorzystaj uwierzytelnianie 2-czynnikowe. Może to być bardziej irytujące i nachalne, ale w niektórych sytuacjach związanych z przestrzeganiem przepisów jest to nieuniknione.

dcgregorya
źródło
2

To jest dodatkowa odpowiedź dla przybywających tutaj na podstawie tytułu pytania (tak jak ja) i nie muszą zajmować się kwestiami bezpieczeństwa związanymi z zapisywaniem haseł.

Jak korzystać z Preferencji współdzielonych

Ustawienia użytkownika są zazwyczaj zapisywane lokalnie w systemie Android przy użyciu SharedPreferencespary klucz-wartość. StringKlawisz służy do zapisywania lub wyszukiwania powiązanej wartości.

Napisz do wspólnych preferencji

String key = "myInt";
int valueToSave = 10;

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, valueToSave).commit();

Użyj apply()zamiast, commit()aby zapisać w tle, a nie natychmiast.

Czytaj z preferencji wspólnych

String key = "myInt";
int defaultValue = 0;

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
int savedValue = sharedPref.getInt(key, defaultValue);

Wartość domyślna jest używana, jeśli klucz nie zostanie znaleziony.

Notatki

  • Zamiast używać lokalnego klucza String w wielu miejscach, tak jak ja powyżej, lepiej byłoby użyć stałej w jednym miejscu. Na górze swojej aktywności ustawień możesz użyć czegoś takiego:

    final static String PREF_MY_INT_KEY = "myInt";
  • Kiedyś intw moim przykładzie, ale można również użyć putString(), putBoolean(), getString(), getBoolean(), itd.

  • Więcej informacji znajduje się w dokumentacji .
  • Istnieje wiele sposobów uzyskania SharedPreferences. Zobacz tę odpowiedź, na co zwrócić uwagę.
Suragch
źródło
1

Ta odpowiedź oparta jest na sugerowanym podejściu Marka. Tworzona jest niestandardowa wersja klasy EditTextPreference, która konwertuje tam iz powrotem między zwykłym tekstem widocznym w widoku a zaszyfrowaną wersją hasła przechowywaną w pamięci preferencji.

Jak zauważyło większość, którzy odpowiedzieli na ten wątek, nie jest to bardzo bezpieczna technika, chociaż stopień bezpieczeństwa zależy częściowo od użytego kodu szyfrowania / deszyfrowania. Ale jest to dość proste i wygodne i udaremni najbardziej zwyczajne węszenie.

Oto kod niestandardowej klasy 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);
    }
}

To pokazuje, jak można z niego korzystać - jest to plik „items”, który steruje wyświetlaniem preferencji. Uwaga: zawiera trzy zwykłe widoki EditTextPreference i jeden niestandardowy widok 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>

Co do faktycznego szyfrowania / deszyfrowania, pozostaje to ćwiczenie dla czytelnika. Obecnie używam kodu na podstawie tego artykułu http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/ , chociaż z różnymi wartościami dla klucza i wektora inicjalizacji.

RenniePet
źródło
1

Przede wszystkim uważam, że dane użytkownika nie powinny być przechowywane na telefonie, a jeśli trzeba je przechowywać w telefonie, należy je zaszyfrować w prywatnych aplikacjach. Priorytetem aplikacji powinno być bezpieczeństwo poświadczeń użytkowników.

Wrażliwe dane powinny być przechowywane bezpiecznie lub wcale. W przypadku zainfekowania zagubionego urządzenia lub złośliwego oprogramowania dane przechowywane w sposób niepewny mogą zostać naruszone.

Yauraw Gadav
źródło
1

Używam Android KeyStore do szyfrowania hasła za pomocą RSA w trybie EBC, a następnie zapisuję je w Preferencjach współdzielonych.

Kiedy chcę odzyskać hasło, czytam zaszyfrowane hasło z SharedPreferences i odszyfrowuję je za pomocą magazynu kluczy.

Za pomocą tej metody generujesz publiczną / prywatną parę kluczy, w której prywatny jest bezpiecznie przechowywany i zarządzany przez system Android.

Oto link, jak to zrobić: Android KeyStore Tutorial

Braveblacklion
źródło
-2

wspólne preferencje to najprostszy sposób na przechowywanie danych naszej aplikacji. ale możliwe jest, że każdy może wyczyścić nasze wspólne dane preferencji za pośrednictwem menedżera aplikacji. więc nie sądzę, że jest to całkowicie bezpieczne dla naszej aplikacji.

Rohit Jain
źródło
-3

musisz użyć sqlite, apit bezpieczeństwa do przechowywania haseł. oto najlepszy przykład, w którym przechowywane są hasła, - passwordsafe. tutaj jest link do źródła i objaśnienia - http://code.google.com/p/android-passwordsafe/

Artem Russakovskii
źródło
3
OP musi przechowywać jedną nazwę użytkownika i hasło. Byłoby absurdalnie rozważyć utworzenie całej tabeli bazy danych dla tego jednego zastosowania
HXCaine
@HXCaine z szacunkiem się nie zgadzam - widzę co najmniej 1 inne użycie tabeli sqlite użytkownika / hasła. JEŚLI UWAŻASZ RYZYKO (korzystania z sqlite) za AKCEPTOWALNE, oprócz prostego uwierzytelnienia logowania do aplikacji, możesz na przykład użyć tabeli do przechowywania wielu haseł ftp (jeśli Twoja aplikacja używa ftp - moje robią to czasami). Ponadto utworzenie klasy adaptera sqlite do tej manipulacji jest proste.
Tony Gil
Miłe wskrzeszenie dwuletniego komentarza! Szczerze mówiąc, mój komentarz był rok po odpowiedzi :) Nawet przy garstce haseł FTP obciążenie jest znacznie większe w przypadku tabeli SQLite niż w przypadku SharedPreferences zarówno pod względem miejsca, jak i kodowania. Z pewnością nie może to być konieczne
HXCaine