Cách thích hợp nhất để lưu trữ cài đặt người dùng trong ứng dụng Android là gì


308

Tôi đang tạo một ứng dụng kết nối với máy chủ bằng tên người dùng / mật khẩu và tôi muốn bật tùy chọn "Lưu mật khẩu" để người dùng không phải nhập mật khẩu mỗi khi ứng dụng khởi động.

Tôi đã cố gắng thực hiện với Shared Preferences nhưng không chắc đây có phải là giải pháp tốt nhất không.

Tôi sẽ đánh giá cao bất kỳ đề xuất nào về cách lưu trữ giá trị / cài đặt của người dùng trong ứng dụng Android.

Câu trả lời:


233

Nói chung SharedPreferences là đặt cược tốt nhất của bạn để lưu trữ tùy chọn, vì vậy, nói chung tôi khuyên bạn nên sử dụng phương pháp này để lưu cài đặt ứng dụng và người dùng.

Khu vực quan tâm duy nhất ở đây là những gì bạn đang tiết kiệm. Mật khẩu luôn là một thứ khó lưu trữ và tôi đặc biệt cảnh giác khi lưu trữ chúng dưới dạng văn bản rõ ràng. Kiến trúc Android sao cho SharedPreferences của ứng dụng của bạn được đóng hộp cát để ngăn các ứng dụng khác không thể truy cập các giá trị để có một số bảo mật ở đó, nhưng quyền truy cập vật lý vào điện thoại có thể cho phép truy cập vào các giá trị.

Nếu có thể, tôi sẽ xem xét sửa đổi máy chủ để sử dụng mã thông báo được đàm phán để cung cấp quyền truy cập, đại loại như OAuth . Ngoài ra, bạn có thể cần phải xây dựng một số loại cửa hàng mật mã, mặc dù điều đó không tầm thường. Ít nhất, hãy chắc chắn rằng bạn đang mã hóa mật khẩu trước khi ghi nó vào đĩa.


3
Bạn có thể vui lòng giải thích những gì bạn có nghĩa là bằng hộp cát?
Abhijit

14
một chương trình hộp cát là bất kỳ ứng dụng nào có quá trình và thông tin (như các tùy chọn được chia sẻ) vẫn bị ẩn khỏi các ứng dụng còn lại. Một ứng dụng Android chạy trong một gói không thể truy cập trực tiếp vào bất cứ thứ gì trong gói khác. Đó là lý do tại sao các ứng dụng trong cùng một gói (luôn là của bạn) có thể truy cập thông tin từ những ứng dụng khác
Korcholis

@Reto Meier yêu cầu của tôi là bảo vệ các dịch vụ web có sẵn công khai cho việc tôi đang sử dụng mã thông báo, việc lưu trữ nó trên các tùy chọn chia sẻ có an toàn không? Tôi có một máy thu phát khởi động trong ứng dụng của mình, nó sẽ xóa tất cả dữ liệu chia sẻ nếu nó tìm thấy thiết bị đã được root. Điều này có đủ để bảo vệ mã thông báo của tôi.
pyus13

1
Mỗi android-developers.blogspot.com/2013/02/ trên , thông tin đăng nhập của người dùng nên được lưu trữ với cờ MODE_PRIVATE được lưu trữ và lưu trữ trong bộ nhớ trong (với cùng một lưu ý về việc lưu trữ bất kỳ loại mật khẩu nào được mở cục bộ để tấn công). Điều đó nói rằng, việc sử dụng MODE_PRIVATEvới SharedPreferences tương đương với việc làm tương tự với một tệp được tạo trên bộ nhớ trong, về mặt hiệu quả để làm xáo trộn dữ liệu được lưu trữ cục bộ?
qix

6
Không lưu trữ mật khẩu trong các tùy chọn chia sẻ. Nếu người dùng mất điện thoại, họ đã mất mật khẩu. Nó sẽ được đọc. Nếu họ sử dụng mật khẩu đó ở nơi khác, mọi nơi họ sử dụng đều bị xâm phạm. Ngoài ra, bạn đã mất vĩnh viễn tài khoản này vì với mật khẩu, họ có thể thay đổi mật khẩu của bạn. Cách chính xác để làm điều này là gửi mật khẩu lên máy chủ một lần và nhận lại mã thông báo đăng nhập. Lưu trữ trong sở thích được chia sẻ và gửi nó lên với mỗi yêu cầu. Nếu mã thông báo đó bị xâm phạm, không có gì khác bị mất.
Gabe Sechan

211

Tôi đồng ý với Reto và fiXedd. Nói một cách khách quan, sẽ không có ý nghĩa gì khi đầu tư thời gian và công sức đáng kể vào việc mã hóa mật khẩu trong SharedPreferences vì ​​bất kỳ kẻ tấn công nào có quyền truy cập vào tệp tùy chọn của bạn đều có khả năng truy cập vào tệp nhị phân của ứng dụng của bạn và do đó, các khóa để giải mã mật khẩu.

Tuy nhiên, điều đó đang được nói, dường như có một sáng kiến ​​công khai về việc xác định các ứng dụng di động lưu trữ mật khẩu của họ trong văn bản rõ ràng trong SharedPreferences và chiếu ánh sáng bất lợi vào các ứng dụng đó. Xem http://bloss.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/http://viaforensics.com/appwatchdog để biết một số ví dụ.

Mặc dù chúng ta cần chú ý nhiều hơn đến bảo mật nói chung, tôi sẽ lập luận rằng loại chú ý này về một vấn đề cụ thể này không thực sự làm tăng đáng kể an ninh chung của chúng ta. Tuy nhiên, nhận thức như hiện tại, đây là một giải pháp để mã hóa dữ liệu bạn đặt trong SharedPreferences.

Chỉ cần bọc đối tượng SharedPreferences của riêng bạn trong mục này và mọi dữ liệu bạn đọc / ghi sẽ được tự động mã hóa và giải mã. ví dụ.

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

Đây là mã cho lớp:

/**
 * 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
FYI Base64 có sẵn trong API cấp 8 (2.2) trở lên. Bạn có thể sử dụng iharder.sourceforge.net/civerse/java/base64 hoặc một cái gì đó khác cho các hệ điều hành trước đó.
emmby

34
Vâng, tôi đã viết này. Thoải mái sử dụng, không cần ghi công
emmby

8
Tôi đồng ý với bạn. Nhưng nếu mật khẩu chỉ được sử dụng trên máy chủ, tại sao không sử dụng mã hóa khóa Công khai / riêng tư? Khóa công khai trên máy khách khi lưu mật khẩu. Khách hàng sẽ không bao giờ phải đọc mật khẩu văn bản rõ ràng nữa, phải không? Sau đó, máy chủ có thể giải mã nó bằng khóa riêng. Vì vậy, ngay cả khi ai đó duyệt mã nguồn ứng dụng của bạn, họ cũng không thể lấy được mật khẩu, ngoại trừ họ hack máy chủ của bạn và lấy khóa riêng.
Patrick Boos

4
Tôi đã thêm một vài tính năng vào mã này và đặt nó trên github tại github.com/RightHandedMonkey/WorxForUs_L Library / blob / master / src / . Bây giờ nó xử lý việc di chuyển một tùy chọn không được mã hóa sang tùy chọn được mã hóa. Ngoài ra, nó tạo khóa trong thời gian chạy, do đó, dịch ngược ứng dụng không giải phóng khóa.
RightHandedMonkey

3
Ngoài ra, nhưng nhận xét của @PatrickBoos là một ý tưởng tuyệt vời. Tuy nhiên, một vấn đề với điều này là mặc dù bạn đã mã hóa mật khẩu, nhưng kẻ tấn công đã đánh cắp mật mã đó vẫn có thể đăng nhập vào máy chủ của bạn, vì máy chủ của bạn thực hiện giải mã. Một bổ sung cho phương pháp này là mã hóa mật khẩu cùng với dấu thời gian. Bằng cách đó, bạn có thể quyết định, chỉ cho phép mật khẩu được lưu trong quá khứ gần đây (như thêm ngày hết hạn vào "mã thông báo" của bạn) hoặc thậm chí yêu cầu một số người dùng nhất định phải có dấu thời gian kể từ một ngày cụ thể (hãy để bạn "thu hồi" "mã thông báo" cũ).
adevine

29

Về cách đơn giản nhất để lưu trữ một sở thích duy nhất trong Hoạt động Android là làm một cái gì đó như thế này:

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

Nếu bạn lo lắng về tính bảo mật của chúng thì bạn luôn có thể mã hóa mật khẩu trước khi lưu trữ.


9
Tôi không thể đồng ý với bạn nhiều hơn về phương pháp đơn giản này; tuy nhiên, bạn nên luôn lo lắng về tính bảo mật của mật khẩu mà bạn lưu trữ? Tùy thuộc vào ứng dụng của bạn, bạn có các khoản nợ tiềm tàng đối với thông tin cá nhân bị đánh cắp. Chỉ cần chỉ ra điều này cho bất kỳ ai đang cố gắng lưu trữ mật khẩu thực tế vào những thứ như tài khoản ngân hàng hoặc thứ gì đó quan trọng không kém. Tôi vẫn bầu bạn mặc dù.
While-E

1
Nơi nào bạn sẽ lưu trữ khóa lưu trữ mật khẩu? Nếu người dùng khác có thể truy cập các tùy chọn chia sẻ, thì đó là chìa khóa.
OrhanC1

@ OrhanC1 bạn đã có câu trả lời.?
eRaisedToX

10

Sử dụng đoạn mã do Richard cung cấp, bạn có thể mã hóa mật khẩu trước khi lưu nó. Tuy nhiên, API tùy chọn không cung cấp một cách dễ dàng để chặn giá trị và mã hóa nó - bạn có thể chặn nó đang được lưu thông qua trình nghe OnPreferenceChange và về mặt lý thuyết bạn có thể sửa đổi nó thông qua một sở thíchChangeListener, nhưng điều đó dẫn đến một vòng lặp vô tận.

Tôi trước đó đã đề nghị thêm một ưu tiên "ẩn" để thực hiện điều này. Đó chắc chắn không phải là cách tốt nhất. Tôi sẽ trình bày hai lựa chọn khác mà tôi cho là khả thi hơn.

Đầu tiên, đơn giản nhất, là trong một ưu tiênChangeListener, bạn có thể lấy giá trị đã nhập, mã hóa nó và sau đó lưu nó vào một tệp tùy chọn thay thế:

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

Cách thứ hai và cách tôi thích bây giờ là tạo tùy chọn tùy chỉnh của riêng bạn, mở rộng EditTextPreference, @ Ghi đè lên setText()getText()các phương thức, để setText()mã hóa mật khẩu và getText()trả về null.


Tôi biết điều này khá cũ, nhưng bạn có phiền khi đăng mã của mình cho phiên bản EditTextPreference tùy chỉnh không?
RenniePet

Đừng bận tâm, tôi đã tìm thấy một mẫu có thể sử dụng ở đây Groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6M và tôi đã làm cho nó hoạt động ngay bây giờ. Cảm ơn đã gợi ý cách tiếp cận này.
RenniePet

6

Được chứ; đã được một thời gian kể từ khi câu trả lời là hỗn hợp, nhưng đây là một vài câu trả lời phổ biến. Tôi đã nghiên cứu điều này như điên và thật khó để xây dựng một câu trả lời hay

  1. Phương thức MODE_PRIVATE được coi là an toàn, nếu bạn cho rằng người dùng không root thiết bị. Dữ liệu của bạn được lưu trữ trong văn bản thuần túy trong một phần của hệ thống tệp chỉ có thể được truy cập bởi chương trình gốc. Điều này makings lấy mật khẩu với một ứng dụng khác trên một thiết bị đã root. Sau đó, một lần nữa, bạn có muốn hỗ trợ các thiết bị đã root?

  2. AES vẫn là mã hóa tốt nhất bạn có thể làm. Hãy nhớ tìm kiếm điều này nếu bạn đang bắt đầu triển khai mới nếu đã được một thời gian kể từ khi tôi đăng bài này. Vấn đề lớn nhất với điều này là "Phải làm gì với khóa mã hóa?"

Vì vậy, bây giờ chúng ta đang ở "Làm gì với chìa khóa?" phần. Đây là phần cứng. Lấy chìa khóa hóa ra không phải là xấu. Bạn có thể sử dụng chức năng phái sinh khóa để lấy một số mật khẩu và biến nó thành một khóa khá an toàn. Bạn có gặp phải các vấn đề như "bạn làm được bao nhiêu lượt với PKFDF2 không?", Nhưng đó là một chủ đề khác

  1. Lý tưởng nhất là bạn lưu trữ khóa AES khỏi thiết bị. Bạn phải tìm ra một cách tốt để lấy khóa từ máy chủ một cách an toàn, đáng tin cậy và an toàn mặc dù

  2. Bạn có một chuỗi đăng nhập của một số loại (ngay cả chuỗi đăng nhập ban đầu bạn làm để truy cập từ xa). Bạn có thể thực hiện hai lần chạy trình tạo khóa của mình trên cùng một mật khẩu. Làm thế nào điều này hoạt động là bạn lấy được khóa hai lần với một muối mới và một vectơ khởi tạo an toàn mới. Bạn lưu trữ một trong những mật khẩu được tạo trên thiết bị và bạn sử dụng mật khẩu thứ hai làm khóa AES.

Khi bạn đăng nhập, bạn lấy lại khóa trên thông tin đăng nhập cục bộ và so sánh nó với khóa được lưu trữ. Khi đã xong, bạn sử dụng khóa dẫn xuất số 2 cho AES.

  1. Sử dụng phương pháp "nói chung an toàn", bạn mã hóa dữ liệu bằng AES và lưu trữ khóa trong MODE_PRIVATE. Điều này được khuyến nghị bởi một bài đăng trên blog gần đây của Android. Không phải cực kỳ an toàn, nhưng cách tốt hơn cho một số người qua văn bản thuần túy

Bạn có thể làm rất nhiều biến thể của những điều này. Ví dụ: thay vì một chuỗi đăng nhập đầy đủ, bạn có thể thực hiện mã PIN nhanh (dẫn xuất). Mã PIN nhanh có thể không an toàn như một chuỗi đăng nhập đầy đủ, nhưng nó an toàn hơn nhiều lần so với văn bản thuần túy


5

Tôi biết đây là một chút gọi hồn, nhưng bạn nên sử dụng Android lý Tài khoản . Đó là mục đích được xây dựng cho kịch bản này. Hơi cồng kềnh một chút nhưng một trong những điều nó làm là vô hiệu hóa thông tin đăng nhập cục bộ nếu thẻ SIM thay đổi, vì vậy nếu ai đó quẹt điện thoại của bạn và ném SIM mới vào đó, thông tin đăng nhập của bạn sẽ không bị xâm phạm.

Điều này cũng cung cấp cho người dùng một cách nhanh chóng và dễ dàng để truy cập (và có khả năng xóa) thông tin đăng nhập được lưu trữ cho bất kỳ tài khoản nào họ có trên thiết bị, tất cả từ một nơi.

SampleSyncAdOG là một ví dụ sử dụng thông tin đăng nhập tài khoản được lưu trữ.


1
Xin lưu ý rằng việc sử dụng AccountManager không an toàn hơn bất kỳ phương pháp nào khác được cung cấp ở trên! developer.android.com/training/id-auth/
Mạnh

1
Trường hợp sử dụng cho AccountManager là khi tài khoản phải được chia sẻ giữa các ứng dụng khác nhau và các ứng dụng từ các tác giả khác nhau. Lưu trữ mật khẩu và cung cấp cho bất kỳ ứng dụng yêu cầu nào sẽ không phù hợp. Nếu việc sử dụng người dùng / mật khẩu chỉ dành cho một ứng dụng, đừng sử dụng AccountManager.
heo

1
@dolmen, điều đó không hoàn toàn chính xác. AccountManager sẽ không cung cấp mật khẩu tài khoản cho bất kỳ ứng dụng nào có UID không khớp với Authenticator. Tên, vâng; mã thông báo xác thực, có; mật khẩu, không. Nếu bạn thử, nó sẽ ném SecurityException. Và trường hợp sử dụng rộng hơn thế nhiều. developer.android.com/training/id-auth/identify.html
Jon O

5

Tôi sẽ ném mũ vào vòng chỉ để nói về việc bảo mật mật khẩu nói chung trên Android. Trên Android, nhị phân thiết bị nên được xem là bị xâm phạm - điều này cũng tương tự đối với bất kỳ ứng dụng cuối nào nằm trong sự kiểm soát trực tiếp của người dùng. Về mặt khái niệm, một hacker có thể sử dụng quyền truy cập cần thiết vào nhị phân để dịch ngược nó và lấy mật khẩu được mã hóa của bạn, v.v.

Vì vậy, có hai gợi ý tôi muốn đưa ra ngoài nếu bảo mật là mối quan tâm chính của bạn:

1) Không lưu trữ mật khẩu thực tế. Lưu trữ mã thông báo truy cập được cấp và sử dụng mã thông báo truy cập và chữ ký của điện thoại để xác thực phía máy chủ phiên. Lợi ích của việc này là bạn có thể làm cho mã thông báo có thời lượng giới hạn, bạn không xâm phạm mật khẩu ban đầu và bạn có một chữ ký tốt mà bạn có thể sử dụng để tương quan với lưu lượng truy cập sau này (ví dụ: kiểm tra các nỗ lực xâm nhập và vô hiệu hóa mã thông báo hiển thị nó vô dụng).

2) Sử dụng xác thực 2 yếu tố. Điều này có thể gây phiền nhiễu và xâm phạm hơn nhưng đối với một số tình huống tuân thủ là không thể tránh khỏi.



2

Đây là câu trả lời bổ sung cho những người đến đây dựa trên tiêu đề câu hỏi (như tôi đã làm) và không cần phải giải quyết các vấn đề bảo mật liên quan đến lưu mật khẩu.

Cách sử dụng Tùy chọn chia sẻ

Cài đặt người dùng thường được lưu cục bộ trong Android bằng SharedPreferencescặp giá trị khóa. Bạn sử dụng Stringkhóa để lưu hoặc tra cứu giá trị liên quan.

Viết thư cho Sở thích được chia sẻ

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

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

Sử dụng apply()thay vì commit()để lưu trong nền chứ không phải ngay lập tức.

Đọc từ Sở thích được chia sẻ

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

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

Giá trị mặc định được sử dụng nếu không tìm thấy khóa.

Ghi chú

  • Thay vì sử dụng Chuỗi khóa cục bộ ở nhiều nơi như tôi đã làm ở trên, sẽ tốt hơn nếu sử dụng hằng số ở một vị trí. Bạn có thể sử dụng một cái gì đó như thế này ở đầu hoạt động cài đặt của bạn:

    final static String PREF_MY_INT_KEY = "myInt";
  • Tôi đã sử dụng một inttrong ví dụ của tôi, nhưng bạn cũng có thể sử dụng putString(), putBoolean(), getString(), getBoolean()vv

  • Xem tài liệu để biết thêm chi tiết.
  • Có nhiều cách để có được SharedPreferences. Xem câu trả lời này cho những gì cần chú ý.

1

Câu trả lời này dựa trên cách tiếp cận được đề xuất bởi Mark. Một phiên bản tùy chỉnh của lớp EditTextPreference được tạo để chuyển đổi qua lại giữa văn bản đơn giản nhìn thấy trong chế độ xem và phiên bản được mã hóa của mật khẩu được lưu trữ trong bộ lưu trữ tùy chọn.

Như đã được chỉ ra bởi hầu hết những người đã trả lời về chủ đề này, đây không phải là một kỹ thuật rất an toàn, mặc dù mức độ bảo mật phụ thuộc một phần vào mã hóa / giải mã được sử dụng. Nhưng nó khá đơn giản và tiện lợi, và sẽ cản trở hầu hết việc rình mò.

Đây là mã cho lớp EditTextPreference tùy chỉnh:

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

Điều này cho thấy cách nó có thể được sử dụng - đây là tệp "mục" điều khiển màn hình tùy chọn. Lưu ý rằng nó chứa ba khung nhìn EditTextPreference thông thường và một trong các khung nhìn EditPasswordPreference tùy chỉnh.

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

Đối với mã hóa / giải mã thực tế, đó là một bài tập cho người đọc. Tôi hiện đang sử dụng một số mã dựa trên bài viết này http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-pl platform-java-and-c-encoding-compabilities / , mặc dù với các giá trị khác nhau cho khóa và vectơ khởi tạo.


1

Trước hết tôi nghĩ dữ liệu của Người dùng không nên được lưu trữ trên điện thoại và nếu phải lưu trữ dữ liệu ở đâu đó trên điện thoại thì dữ liệu đó phải được mã hóa trong dữ liệu riêng tư của ứng dụng. Bảo mật thông tin đăng nhập của người dùng nên là ưu tiên của ứng dụng.

Các dữ liệu nhạy cảm nên được lưu trữ an toàn hoặc không. Trong trường hợp thiết bị bị mất hoặc nhiễm phần mềm độc hại, dữ liệu được lưu trữ không an toàn có thể bị xâm phạm.


1

Tôi sử dụng KeyStore của Android để mã hóa mật khẩu bằng RSA ở chế độ ECB và sau đó lưu nó trong SharedPreferences.

Khi tôi muốn lấy lại mật khẩu, tôi đã đọc mật khẩu từ SharedPreferences và giải mã nó bằng KeyStore.

Với phương pháp này, bạn tạo cặp Khóa công khai / riêng tư trong đó khóa riêng được lưu trữ và quản lý an toàn bởi Android.

Đây là một liên kết về cách làm điều này: Hướng dẫn Android KeyStore


-2

chia sẻ sở thích là cách dễ nhất để lưu trữ dữ liệu ứng dụng của chúng tôi. nhưng có thể là bất cứ ai cũng có thể xóa dữ liệu tùy chọn chia sẻ của chúng tôi thông qua trình quản lý ứng dụng. Vì vậy, tôi không nghĩ rằng nó hoàn toàn an toàn cho ứng dụng của chúng tôi.


-3

bạn cần sử dụng sqlite, apit bảo mật để lưu trữ mật khẩu. đây là ví dụ tốt nhất, nơi lưu trữ mật khẩu, - passwordafe. đây là liên kết cho nguồn và giải thích - http://code.google.com.vn/p/android-passwordsafe/


3
OP cần lưu trữ một cặp tên người dùng và mật khẩu. Sẽ thật nực cười khi xem xét việc tạo toàn bộ bảng cơ sở dữ liệu cho lần sử dụng này
HXCaine

@HXCaine tôi không đồng ý - tôi có thể thấy ít nhất 1 lần sử dụng khác của bảng sqlite người dùng / mật khẩu. NẾU BẠN XÁC NHẬN RỦI RO (sử dụng sqlite) CHẤP NHẬN, ngoài xác thực đăng nhập ứng dụng đơn giản, bạn có thể sử dụng bảng để lưu trữ nhiều mật khẩu ftp (nếu ứng dụng của bạn đôi khi sử dụng ftp - của tôi). cộng với việc tạo một lớp bộ điều hợp sqlite cho thao tác này là đơn giản.
tony gil

Đẹp hồi sinh của một bình luận hai năm tuổi! Công bằng mà nói, nhận xét của tôi là một năm sau câu trả lời :) Ngay cả với một số mật khẩu FTP, tổng chi phí lớn hơn nhiều với bảng SQLite so với SharedPreferences cả về không gian và mã hóa. Chắc chắn điều đó là không cần thiết
HXCaine
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.