วิธีที่เหมาะสมที่สุดในการจัดเก็บการตั้งค่าผู้ใช้ในแอปพลิเคชัน Android คืออะไร


308

ฉันกำลังสร้างแอปพลิเคชันที่เชื่อมต่อกับเซิร์ฟเวอร์โดยใช้ชื่อผู้ใช้ / รหัสผ่านและฉันต้องการเปิดใช้งานตัวเลือก "บันทึกรหัสผ่าน" เพื่อให้ผู้ใช้ไม่ต้องพิมพ์รหัสผ่านทุกครั้งที่แอปพลิเคชันเริ่มทำงาน

ฉันพยายามทำมันด้วย Shared Preferences แต่ไม่แน่ใจว่านี่เป็นทางออกที่ดีที่สุดหรือไม่

ฉันขอขอบคุณข้อเสนอแนะใด ๆ เกี่ยวกับวิธีการจัดเก็บค่า / การตั้งค่าผู้ใช้ในแอปพลิเคชัน Android

คำตอบ:


233

โดยทั่วไป SharedPreferences เป็นทางออกที่ดีที่สุดสำหรับการจัดเก็บค่ากำหนดดังนั้นโดยทั่วไปฉันขอแนะนำวิธีการบันทึกแอปพลิเคชันและการตั้งค่าผู้ใช้

ข้อกังวลเดียวของที่นี่คือสิ่งที่คุณกำลังบันทึก รหัสผ่านเป็นสิ่งที่ยุ่งยากในการจัดเก็บเสมอและฉันจะระมัดระวังเป็นพิเศษในการจัดเก็บเป็นข้อความที่ชัดเจน สถาปัตยกรรม Android เป็นเช่นนั้น SharedPreferences แอปพลิเคชันของคุณจะถูก sandboxed เพื่อป้องกันไม่ให้แอปพลิเคชันอื่น ๆ สามารถเข้าถึงค่าเพื่อให้มีความปลอดภัยที่นั่น แต่การเข้าถึงทางกายภาพไปยังโทรศัพท์อาจอนุญาตให้เข้าถึงค่าได้

ถ้าเป็นไปได้ฉันต้องการพิจารณาปรับเปลี่ยนเซิร์ฟเวอร์ที่จะใช้การเจรจา token สำหรับการให้บริการการเข้าถึงสิ่งที่ต้องการOAuth อีกทางหนึ่งคุณอาจต้องสร้างที่เก็บรหัสลับบางประเภทแม้ว่ามันจะไม่สำคัญก็ตาม อย่างน้อยที่สุดให้แน่ใจว่าคุณกำลังเข้ารหัสรหัสผ่านก่อนที่จะเขียนลงดิสก์


3
โปรดอธิบายสิ่งที่คุณหมายถึงโดย sandboxed
อภิสิทธิ์

14
โปรแกรม sandboxed เป็นแอปพลิเคชันที่กระบวนการและข้อมูล (เช่นการตั้งค่าที่ใช้ร่วมกันเหล่านั้น) ยังคงซ่อนอยู่จากส่วนที่เหลือของแอปพลิเคชัน แอปพลิเคชัน android ที่ทำงานในแพ็คเกจไม่สามารถเข้าถึงสิ่งใด ๆ ภายในแพ็คเกจอื่นได้โดยตรง นั่นเป็นเหตุผลที่แอปพลิเคชันในแพ็คเกจเดียวกัน (ซึ่งเป็นของคุณเสมอ) สามารถเข้าถึงข้อมูลจากแอปอื่น ๆ ได้
Korcholis

@Reto Meier ความต้องการของฉันคือการปกป้องบริการเว็บสาธารณะที่ฉันใช้โทเค็นการจัดเก็บไว้ในการตั้งค่าที่ใช้ร่วมกันมีความปลอดภัยหรือไม่ ฉันมีตัวรับสัญญาณบรอดอัพ bootup ในแอปพลิเคชันของฉันซึ่งจะลบข้อมูลการแชร์การประชุมทั้งหมดหากพบว่าอุปกรณ์ดังกล่าวเป็นรูท เพียงพอที่จะปกป้องโทเค็นของฉันหรือไม่
pyus13

1
ต่อandroid-developers.blogspot.com/2013/02/... , ข้อมูลประจำตัวของผู้ใช้ควรจะเก็บไว้กับชุดธง MODE_PRIVATE และเก็บไว้ในที่เก็บข้อมูลภายใน (ที่มีคำเตือนเดียวกันเกี่ยวกับการจัดเก็บการจัดเรียงของรหัสผ่านใด ๆ ในประเทศในที่สุดเปิดให้โจมตี) ที่กล่าวว่าใช้MODE_PRIVATEกับ SharedPreferences เทียบเท่ากับการสร้างไฟล์บนที่เก็บข้อมูลภายในในแง่ของประสิทธิภาพในการทำให้ข้อมูลที่เก็บไว้ในเครื่องงงงวยหรือไม่
qix

6
อย่าเก็บรหัสผ่านในการตั้งค่าที่ใช้ร่วมกัน หากผู้ใช้สูญเสียโทรศัพท์พวกเขาสูญเสียรหัสผ่าน มันจะถูกอ่าน หากพวกเขาใช้รหัสผ่านนั้นที่อื่นทุกที่ที่พวกเขาใช้มันจะถูกบุกรุก นอกจากนี้คุณได้สูญเสียบัญชีนี้อย่างถาวรเพราะด้วยรหัสผ่านที่พวกเขาสามารถเปลี่ยนรหัสผ่านของคุณ วิธีที่ถูกต้องในการทำเช่นนี้คือการส่งรหัสผ่านไปยังเซิร์ฟเวอร์หนึ่งครั้งและรับโทเค็นเข้าสู่ระบบกลับ เก็บไว้ในการตั้งค่าที่ใช้ร่วมกันและส่งขึ้นมาพร้อมกับคำขอ หากโทเค็นนั้นถูกโจมตีจะไม่มีสิ่งใดสูญหาย
Gabe Sechan

211

ฉันเห็นด้วยกับ Reto และ fiXedd การพูดอย่างเป็นกลางมันไม่สมเหตุสมผลในการลงทุนเวลาและความพยายามอย่างมากในการเข้ารหัสรหัสผ่านใน SharedPreferences เนื่องจากผู้โจมตีที่เข้าถึงไฟล์กำหนดค่าของคุณนั้นมีแนวโน้มที่จะเข้าถึงไบนารีของแอปพลิเคชันของคุณด้วยเช่นกัน รหัสผ่าน

อย่างไรก็ตามที่ถูกกล่าวว่าดูเหมือนจะมีความคิดริเริ่มการประชาสัมพันธ์ที่เกิดขึ้นในการระบุแอปพลิเคชันมือถือที่เก็บรหัสผ่านของพวกเขาใน cleartext ใน 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
FYI Base64 มีให้บริการในระดับ API 8 (2.2) และใหม่กว่า คุณสามารถใช้iharder.sourceforge.net/current/java/base64หรืออย่างอื่นสำหรับระบบปฏิบัติการก่อนหน้านี้
emmby

34
ใช่ฉันเขียนสิ่งนี้ อย่าลังเลที่จะระบุแหล่งที่มา
emmby

8
ฉันเห็นด้วยกับคุณ. แต่ถ้าใช้รหัสผ่านบนเซิร์ฟเวอร์เท่านั้นทำไมไม่ใช้การเข้ารหัสคีย์สาธารณะ / ส่วนตัว พับลิกคีย์บนไคลเอ็นต์เมื่อบันทึกรหัสผ่าน ลูกค้าจะไม่ต้องอ่านรหัสผ่านที่ชัดเจนอีกครั้งใช่มั้ย เซิร์ฟเวอร์สามารถถอดรหัสได้ด้วยรหัสส่วนตัว ดังนั้นแม้ว่าใครบางคนจะต้องผ่านซอร์สโค้ดแอปของคุณพวกเขาไม่สามารถรับรหัสผ่านได้ยกเว้นพวกเขาแฮกเซิร์ฟเวอร์ของคุณและรับรหัสส่วนตัว
Patrick Boos

4
ฉันได้เพิ่มคุณสมบัติบางประการในรหัสนี้และวางไว้บน github ที่github.com/RightHandedMonkey/WorxForUs_Library/blob/master/src/ … ตอนนี้จะจัดการการย้ายการตั้งค่าที่ไม่ได้เข้ารหัสไปที่การเข้ารหัส อีกทั้งยังสร้างรหัสเมื่อรันไทม์ดังนั้นการถอดรหัสแอปจะไม่ปล่อยคีย์
RightHandedMonkey

3
นอกจากนี้สาย แต่ความคิดเห็นโดย @PatrickBoos เป็นความคิดที่ดี แม้ว่าปัญหาหนึ่งของเรื่องนี้คือแม้ว่าคุณจะเข้ารหัสรหัสผ่านแล้วก็ตามผู้โจมตีที่ขโมยรหัสนั้นจะยังสามารถเข้าสู่เซิร์ฟเวอร์ของคุณได้เนื่องจากเซิร์ฟเวอร์ของคุณทำการถอดรหัส หนึ่งในวิธีการนี้คือการเข้ารหัสรหัสผ่านพร้อมกับการประทับเวลา ด้วยวิธีนี้คุณสามารถตัดสินใจได้ว่าจะอนุญาตให้เฉพาะรหัสผ่านที่บันทึกไว้ในอดีตที่ผ่านมา (เช่นการเพิ่มวันที่หมดอายุลงใน "โทเค็น" ของคุณ) หรือแม้กระทั่งกำหนดให้ผู้ใช้บางคนต้องมีการประทับเวลาตั้งแต่วันที่เฉพาะ "โทเค็นเก่า")
adevine

29

เกี่ยวกับวิธีที่ง่ายที่สุดในการจัดเก็บการตั้งค่าเดียวในกิจกรรม Android คือการทำสิ่งนี้:

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

หากคุณกังวลเกี่ยวกับความปลอดภัยของรหัสเหล่านี้คุณสามารถเข้ารหัสรหัสผ่านได้เสมอก่อนจัดเก็บ


9
ฉันไม่เห็นด้วยกับคุณเกี่ยวกับวิธีการแบบง่าย ๆ นี้ อย่างไรก็ตามคุณควรกังวลเกี่ยวกับความปลอดภัยของรหัสผ่านที่คุณจัดเก็บอยู่เสมอ ขึ้นอยู่กับใบสมัครของคุณคุณมีหนี้สินที่อาจเกิดขึ้นกับข้อมูลส่วนบุคคลที่ถูกขโมย เพียงแค่ชี้ให้ผู้ที่พยายามจัดเก็บรหัสผ่านจริงกับสิ่งต่าง ๆ เช่นบัญชีธนาคารหรือสิ่งที่สำคัญเท่าเทียมกัน ฉันยังคงโหวตให้คุณ
E-

1
คุณจะเก็บกุญแจที่เก็บรหัสผ่านไว้ที่ไหน หากการตั้งค่าที่ใช้ร่วมกันสามารถเข้าถึงได้โดยผู้ใช้รายอื่น ๆ ดังนั้นเป็นกุญแจสำคัญ
OrhanC1

@ OrhanC1 คุณได้รับคำตอบใช่ไหม
eRaisedToX

10

การใช้ข้อมูลโค้ดที่ Richard เตรียมไว้ให้คุณสามารถเข้ารหัสรหัสผ่านก่อนบันทึกได้ API การกำหนดค่าตามความชอบไม่ได้ให้วิธีง่ายๆในการดักจับค่าและเข้ารหัสมัน - คุณสามารถปิดกั้นการบันทึกผ่านผู้ฟัง OnPreferenceChange และในทางทฤษฎีแล้วคุณสามารถปรับเปลี่ยนมันผ่านการตั้งค่า PreChangeListener ได้

ก่อนหน้านี้ฉันได้แนะนำให้เพิ่มการตั้งค่า "ซ่อน" เพื่อให้บรรลุสิ่งนี้ มันไม่ใช่วิธีที่ดีที่สุด ฉันจะนำเสนอสองตัวเลือกอื่น ๆ ที่ฉันคิดว่าเป็นไปได้มากขึ้น

อย่างแรกที่ง่ายที่สุดคืออยู่ใน preferencesChangeListener คุณสามารถคว้าค่าที่ป้อนเข้ารหัสมันแล้วบันทึกลงในไฟล์การตั้งค่าทางเลือกอื่น:

  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


ฉันรู้ว่ามันค่อนข้างเก่า แต่คุณจะโพสต์โค้ดสำหรับ EditTextPreference รุ่นที่กำหนดเองได้ไหม
RenniePet

ไม่เป็นไรฉันพบตัวอย่างที่สามารถใช้งานได้ที่นี่groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6Mและตอนนี้ฉันทำงานได้แล้ว ขอบคุณที่แนะนำวิธีการนี้
RenniePet

6

ตกลง; เป็นเวลานานแล้วที่คำตอบนั้นเป็นแบบผสม แต่นี่เป็นคำตอบทั่วไป ฉันค้นคว้าเรื่องนี้อย่างบ้าคลั่งและเป็นการยากที่จะสร้างคำตอบที่ดี

  1. วิธีการ MODE_PRIVATE นั้นถือว่าปลอดภัยโดยทั่วไปถ้าคุณคิดว่าผู้ใช้ไม่ได้รูทอุปกรณ์ ข้อมูลของคุณถูกเก็บไว้ในรูปแบบข้อความธรรมดาในส่วนของระบบไฟล์ที่สามารถเข้าถึงได้โดยโปรแกรมดั้งเดิมเท่านั้น การทำเช่นนี้เป็นการจับรหัสผ่านด้วยแอปอื่นบนอุปกรณ์ที่รูทได้ง่าย จากนั้นอีกครั้งคุณต้องการสนับสนุนอุปกรณ์ที่รูทหรือไม่

  2. AES ยังคงเป็นการเข้ารหัสที่ดีที่สุดที่คุณสามารถทำได้ อย่าลืมมองหาสิ่งนี้หากคุณกำลังเริ่มต้นการใช้งานใหม่หากผ่านไประยะหนึ่งแล้วที่ฉันโพสต์สิ่งนี้ ปัญหาที่ใหญ่ที่สุดของสิ่งนี้คือ "จะทำอย่างไรกับคีย์เข้ารหัส"

ดังนั้นตอนนี้เราอยู่ที่ "จะทำอย่างไรกับกุญแจ?" ส่วน. นี่คือส่วนที่ยาก รับกุญแจกลายเป็นว่าไม่เลว คุณสามารถใช้ฟังก์ชั่นการหากุญแจเพื่อรับรหัสผ่านและทำให้มันเป็นกุญแจที่ปลอดภัย คุณมีปัญหาเช่น "คุณใช้ PKFDF2 ทำอะไรได้บ้าง" แต่นั่นเป็นอีกหัวข้อ

  1. เป็นการดีที่คุณเก็บคีย์ AES ออกจากอุปกรณ์ คุณต้องหาวิธีที่ดีในการดึงรหัสจากเซิร์ฟเวอร์อย่างปลอดภัยเชื่อถือได้และปลอดภัยแม้ว่า

  2. คุณมีลำดับการเข้าสู่ระบบของการเรียงลำดับบางอย่าง (แม้แต่ลำดับการเข้าสู่ระบบดั้งเดิมที่คุณทำเพื่อการเข้าถึงระยะไกล) คุณสามารถสร้างตัวสร้างคีย์ของคุณสองรหัสผ่านด้วยรหัสผ่านเดียวกัน วิธีการทำงานนี้คือคุณได้รับกุญแจสองครั้งด้วยเกลือใหม่และเวกเตอร์เริ่มต้นที่ปลอดภัยใหม่ คุณเก็บรหัสผ่านที่สร้างขึ้นอย่างใดอย่างหนึ่งบนอุปกรณ์และคุณใช้รหัสผ่านที่สองเป็นคีย์ AES

เมื่อคุณเข้าสู่ระบบคุณจะได้รับคีย์บนล็อกอินท้องถิ่นและเปรียบเทียบกับคีย์ที่เก็บไว้ เมื่อทำเสร็จแล้วคุณจะใช้คีย์สืบทอดมา # 2 สำหรับ AES

  1. ด้วยวิธีการที่ "ปลอดภัยโดยทั่วไป" คุณจะเข้ารหัสข้อมูลโดยใช้ AES และเก็บคีย์ไว้ใน MODE_PRIVATE แนะนำโดยโพสต์บล็อกล่าสุดของ Android ไม่ปลอดภัยอย่างไม่น่าเชื่อ แต่เป็นวิธีที่ดีกว่าสำหรับบางคนในข้อความธรรมดา

คุณสามารถทำสิ่งเหล่านี้ได้หลากหลายรูปแบบ ตัวอย่างเช่นแทนที่จะใช้ลำดับการลงชื่อเข้าใช้เต็มรูปแบบคุณสามารถทำ PIN ด่วน (รับมา) PIN ด่วนอาจไม่ปลอดภัยเท่ากับลำดับการลงชื่อเข้าใช้เต็มรูปแบบ แต่มีความปลอดภัยมากกว่าข้อความธรรมดาหลายเท่า


5

ฉันรู้ว่านี่เป็นนิด ๆ หน่อย ๆ ของเวทมนตร์ แต่คุณควรใช้ Android AccountManager มันสร้างขึ้นตามวัตถุประสงค์สำหรับสถานการณ์นี้ มันค่อนข้างยุ่งยากนิดหน่อย แต่สิ่งหนึ่งที่มันทำคือทำให้ข้อมูลประจำตัวในท้องถิ่นนั้นไม่ถูกต้องหากซิมการ์ดเปลี่ยนไปดังนั้นหากใครบางคนกวาดโทรศัพท์ของคุณและโยนซิมใหม่ลงไปมันจะไม่ถูกทำลาย

สิ่งนี้ยังช่วยให้ผู้ใช้สามารถเข้าถึง (และอาจลบ) ข้อมูลรับรองที่เก็บไว้สำหรับบัญชีใด ๆ ที่พวกเขามีบนอุปกรณ์ได้อย่างรวดเร็วและง่ายดายจากที่เดียว

SampleSyncAdapterเป็นตัวอย่างที่ใช้ประโยชน์จากข้อมูลรับรองบัญชีที่เก็บไว้


1
โปรดทราบว่าการใช้ AccountManager นั้นไม่ปลอดภัยกว่าวิธีการอื่นที่ให้ไว้ข้างต้น! developer.android.com/training/id-auth/…
Sander Versluys

1
กรณีการใช้งานของ AccountManager คือเมื่อต้องมีการแชร์บัญชีระหว่างแอพที่ต่างกันและแอพจากผู้เขียนคนละคน การจัดเก็บรหัสผ่านและมอบให้แอปที่ร้องขอใด ๆ จะไม่เหมาะสม หากการใช้งานของผู้ใช้ / รหัสผ่านมีไว้สำหรับแอปเดียวเท่านั้นอย่าใช้ AccountManager
dolmen

1
@dolmen นั่นไม่ถูกต้องนัก AccountManager จะไม่ให้รหัสผ่านบัญชีกับแอพใด ๆ ที่มี UID ไม่ตรงกับของ Authenticator ชื่อใช่; โทเค็นการรับรองความถูกต้องใช่; รหัสผ่านไม่ ถ้าคุณลองมันจะโยน SecurityException และกรณีการใช้งานกว้างกว่านั้นมาก developer.android.com/training/id-auth/identify.html
Jon O

5

ฉันจะโยนหมวกของฉันลงในวงแหวนเพื่อพูดคุยเกี่ยวกับการรักษาความปลอดภัยรหัสผ่านโดยทั่วไปบน Android บน Android ไบนารี่ของอุปกรณ์ควรถูกพิจารณาว่าเป็นอันตราย - นี่เป็นสิ่งเดียวกันสำหรับแอพพลิเคชั่นปลายทางใด ๆ ที่อยู่ในการควบคุมของผู้ใช้โดยตรง แนวคิดแฮ็กเกอร์สามารถใช้การเข้าถึงไบนารีที่จำเป็นในการถอดรหัสและถอดรหัสรหัสผ่านที่คุณเข้ารหัสและอื่น ๆ

ด้วยเหตุนี้จึงมีข้อเสนอแนะสองข้อที่ฉันอยากจะทิ้งหากความปลอดภัยเป็นเรื่องสำคัญสำหรับคุณ:

1) อย่าเก็บรหัสผ่านจริง จัดเก็บโทเค็นการเข้าถึงที่ได้รับและใช้โทเค็นการเข้าถึงและลายเซ็นของโทรศัพท์เพื่อตรวจสอบสิทธิ์เซสชันเซิร์ฟเวอร์ฝั่ง ข้อดีของการทำเช่นนี้คือคุณสามารถทำให้โทเค็นมีระยะเวลา จำกัด คุณจะไม่ยอมให้รหัสผ่านเดิมและคุณมีลายเซ็นที่ดีที่คุณสามารถใช้สัมพันธ์กับการรับส่งข้อมูลในภายหลัง โทเค็นการแสดงผลมันไร้ประโยชน์)

2) ใช้การพิสูจน์ตัวตนแบบ 2 ระดับ สิ่งนี้อาจน่ารำคาญและรบกวนมากขึ้น แต่สำหรับบางสถานการณ์ที่ไม่สามารถหลีกเลี่ยงได้



2

นี่เป็นคำตอบเพิ่มเติมสำหรับผู้ที่มาถึงที่นี่ตามชื่อคำถาม (เช่นฉัน) และไม่จำเป็นต้องจัดการกับปัญหาด้านความปลอดภัยที่เกี่ยวข้องกับการบันทึกรหัสผ่าน

วิธีใช้การตั้งค่าที่ใช้ร่วมกัน

โดยทั่วไปการตั้งค่าผู้ใช้จะถูกบันทึกไว้ในเครื่อง 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);

ค่าเริ่มต้นจะใช้หากไม่พบรหัส

หมายเหตุ

  • แทนที่จะใช้สตริงคีย์ท้องถิ่นในหลาย ๆ สถานที่อย่างที่ฉันทำข้างต้นจะเป็นการดีกว่าถ้าใช้ค่าคงที่ในตำแหน่งเดียว คุณสามารถใช้สิ่งนี้ที่ด้านบนของกิจกรรมการตั้งค่าของคุณ:

    final static String PREF_MY_INT_KEY = "myInt";
  • ผมใช้intในตัวอย่างของฉัน แต่คุณยังสามารถใช้putString(), putBoolean(), getString(), getBoolean()ฯลฯ

  • ดูเอกสารประกอบสำหรับรายละเอียดเพิ่มเติม
  • มีหลายวิธีในการรับ SharedPreferences ดูคำตอบนี้สำหรับสิ่งที่ควรระวัง

1

คำตอบนี้ขึ้นอยู่กับแนวทางที่แนะนำโดย Mark รุ่นที่กำหนดเองของคลาส 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);
    }
}

สิ่งนี้แสดงให้เห็นว่าสามารถใช้งานได้อย่างไร - นี่คือไฟล์ "รายการ" ที่เป็นตัวขับเคลื่อนการกำหนดค่าตามความชอบ หมายเหตุมีมุมมอง 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-encrypt-compatibility/แม้ว่าจะมีค่าต่างกัน สำหรับคีย์และเวกเตอร์เริ่มต้น


1

ก่อนอื่นฉันคิดว่าข้อมูลของผู้ใช้ไม่ควรถูกเก็บไว้ในโทรศัพท์และถ้ามันต้องเก็บข้อมูลที่ไหนสักแห่งบนโทรศัพท์มันควรจะถูกเข้ารหัสด้วยข้อมูลส่วนตัวของแอพ ความปลอดภัยของข้อมูลรับรองผู้ใช้ควรเป็นเรื่องสำคัญของแอปพลิเคชัน

ข้อมูลที่สำคัญควรเก็บไว้อย่างปลอดภัยหรือไม่เลย ในกรณีที่อุปกรณ์สูญหายหรือติดเชื้อมัลแวร์ข้อมูลที่จัดเก็บอย่างไม่ปลอดภัยสามารถถูกบุกรุกได้


1

ฉันใช้ Android KeyStore เพื่อเข้ารหัสรหัสผ่านโดยใช้ RSA ในโหมด ECB จากนั้นบันทึกไว้ใน SharedPreferences

เมื่อฉันต้องการรหัสผ่านฉันอ่านรหัสที่เข้ารหัสจาก SharedPreferences และถอดรหัสโดยใช้ KeyStore

ด้วยวิธีนี้คุณจะสร้างคู่คีย์สาธารณะ / ส่วนตัวที่เก็บข้อมูลส่วนตัวและจัดการโดย Android อย่างปลอดภัย

นี่คือลิงค์เกี่ยวกับวิธีการทำเช่นนี้: Android KeyStore Tutorial


-2

การตั้งค่าที่ใช้ร่วมกันเป็นวิธีที่ง่ายที่สุดในการจัดเก็บข้อมูลแอปพลิเคชันของเรา แต่เป็นไปได้ว่าทุกคนสามารถล้างข้อมูลการตั้งค่าของเราที่ใช้ร่วมกันผ่านตัวจัดการแอปพลิเคชันดังนั้นฉันไม่คิดว่ามันปลอดภัยอย่างสมบูรณ์สำหรับแอปพลิเคชันของเรา


-3

คุณต้องใช้ sqlite, apit ความปลอดภัยในการจัดเก็บรหัสผ่าน นี่คือตัวอย่างที่ดีที่สุดซึ่งเก็บรหัสผ่าน - รหัสผ่านที่ปลอดภัย นี่คือลิงค์สำหรับแหล่งที่มาและคำอธิบาย - http://code.google.com/p/android-passwordsafe/


3
OP ต้องการเก็บชื่อผู้ใช้และรหัสผ่านหนึ่งคู่ มันจะไร้สาระที่จะพิจารณาการสร้างตารางฐานข้อมูลทั้งหมดสำหรับการใช้งานนี้
HXCaine

@HXCaine ฉันไม่เห็นด้วยอย่างเคารพ - ฉันสามารถดูการใช้งานตารางผู้ใช้ / รหัสผ่าน sqlite อย่างน้อย 1 ครั้ง หากคุณพิจารณาความเสี่ยง (จากการใช้ sqlite) ACCEPTABLE นอกเหนือจากการตรวจสอบสิทธิ์การเข้าสู่ระบบแอปพลิเคชันอย่างง่ายคุณสามารถใช้ตารางเพื่อจัดเก็บรหัสผ่าน ftp หลายรายการ (หากแอปของคุณใช้ ftp - mine ทำ) นอกจากนี้การสร้างคลาสอะแดปเตอร์ sqlite สำหรับการจัดการนี้เป็นเรื่องง่าย
tony gil

การฟื้นคืนชีพที่ดีของความคิดเห็นสองปี! ความคิดเห็นของฉันเป็นเวลาหนึ่งปีหลังจากคำตอบ :) แม้จะมีรหัสผ่าน FTP จำนวนหนึ่งค่าใช้จ่ายก็มีขนาดใหญ่กว่าด้วยตาราง SQLite มากกว่าด้วย SharedPreferences ทั้งในแง่ของพื้นที่และการเข้ารหัส แน่นอนว่าไม่จำเป็น
HXCaine
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.