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

클래스 코드는 다음과 같습니다.

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

}


답변

Android 활동에서 단일 환경 설정을 저장하는 가장 간단한 방법은 다음과 같은 작업을 수행하는 것입니다.

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

보안이 걱정된다면 암호를 저장하기 전에 항상 암호화 할 수 있습니다.


답변

Richard가 제공 한 스 니펫을 사용하여 비밀번호를 저장하기 전에 암호화 할 수 있습니다. 그러나 환경 설정 API는 값을 가로 채어 암호화하는 쉬운 방법을 제공하지 않습니다. OnPreferenceChange 리스너를 통해 저장되는 것을 차단할 수 있으며 이론적으로 preferenceChangeListener를 통해 값을 수정할 수는 있지만 무한 루프가 발생합니다.

나는 이것을하기 위해 “숨겨진”환경 설정을 추가 할 것을 제안했다. 확실히 최선의 방법은 아닙니다. 좀 더 실용적이라고 생각되는 다른 두 가지 옵션을 제시하겠습니다.

먼저 가장 간단한 방법은 preferenceChangeListener에 있으며 입력 한 값을 가져 와서 암호화 한 다음 대체 환경 설정 파일에 저장할 수 있습니다.

  public boolean onPreferenceChange(Preference preference, Object newValue) {
      // get our "secure" shared preferences file.
      SharedPreferences secure = context.getSharedPreferences(
         "SECURE",
         Context.MODE_PRIVATE
      );
      String encryptedText = null;
      // encrypt and set the preference.
      try {
         encryptedText = SimpleCrypto.encrypt(Preferences.SEED,(String)newValue);

         Editor editor = secure.getEditor();
         editor.putString("encryptedPassword",encryptedText);
         editor.commit();
      }
      catch (Exception e) {
         e.printStackTrace();
      }
      // always return false.
      return false;
   }

두 번째 방법과 지금 선호하는 방법은 EditTextPreference를 확장하고 @Override ‘ setText()getText()메소드를 사용자 정의 환경 설정을 작성하여 setText()비밀번호 를 암호화하고 getText()null을 리턴하는 것입니다.


답변

괜찮아; 답변이 혼합되어 있기 때문에 오랜 시간이 지났지 만 여기에 몇 가지 일반적인 답변이 있습니다. 나는 이것을 미친 것처럼 연구했고 좋은 대답을 만들기가 어려웠다.

  1. 사용자가 장치를 루팅하지 않았다고 가정하면 MODE_PRIVATE 메서드는 일반적으로 안전한 것으로 간주됩니다. 데이터는 파일 시스템의 일부에 일반 프로그램으로 만 액세스 할 수있는 일반 텍스트로 저장됩니다. 이렇게하면 루팅 된 기기에서 다른 앱으로 비밀번호를 쉽게 잡을 수 있습니다. 그런 다음 다시 루팅 된 장치를 지원 하시겠습니까?

  2. AES는 여전히 최고의 암호화입니다. 내가 게시한지 오래 된 경우 새로운 구현을 시작하는 경우 이것을 찾아보십시오. 가장 큰 문제는 “암호화 키로 무엇을해야합니까?”입니다.

이제 “키로 무엇을해야합니까?” 일부. 어려운 부분입니다. 열쇠를 얻는 것이 그렇게 나쁘지 않은 것으로 판명되었습니다. 키 파생 기능을 사용하여 암호를 가져 와서 꽤 안전한 키로 만들 수 있습니다. “PKFDF2로 몇 번의 패스를 사용합니까?”와 같은 문제가 발생하지만 다른 주제입니다.

  1. 이상적으로는 장치에서 AES 키를 저장합니다. 서버에서 키를 안전하고 안정적이며 안전하게 검색하는 좋은 방법을 찾아야합니다.

  2. 어떤 종류의 로그인 순서 (원격 액세스를 위해 수행 한 원래의 로그인 순서)도 있습니다. 동일한 암호로 키 생성기를 두 번 실행할 수 있습니다. 이것이 작동하는 방식은 새로운 소금과 새로운 안전한 초기화 벡터를 사용하여 키를 두 번 파생시키는 것입니다. 생성 된 비밀번호 중 하나를 디바이스에 저장하고 두 번째 비밀번호를 AES 키로 사용합니다.

로그인하면 로컬 로그인에서 키를 다시 파생시켜 저장된 키와 비교합니다. 이 작업이 완료되면 AES에 파생 키 # 2를 사용합니다.

  1. “일반적으로 안전한”접근 방식을 사용하면 AES를 사용하여 데이터를 암호화하고 MODE_PRIVATE에 키를 저장합니다. 최신 Android 블로그 게시물에서 권장합니다. 엄청나게 안전하지는 않지만 일부 사람들에게는 일반 텍스트보다 더 나은 방법

많은 변형을 할 수 있습니다. 예를 들어, 전체 로그인 순서 대신 빠른 PIN (파생)을 수행 할 수 있습니다. 빠른 PIN은 전체 로그인 시퀀스만큼 안전하지는 않지만 일반 텍스트보다 몇 배 더 안전합니다.


답변

나는 이것이 약간의 괴사라는 것을 알고 있지만 Android AccountManager를 사용해야합니다 . 이 시나리오를 위해 특별히 제작되었습니다. 약간 번거롭지 만 SIM 카드가 변경되면 로컬 자격 증명이 무효화되므로 누군가가 휴대 전화를 스 와이프하고 새로운 SIM을 던지면 자격 증명이 손상되지 않습니다.

또한 사용자는 장치에있는 모든 계정의 저장된 자격 증명을 한 곳에서 쉽고 빠르게 액세스하고 삭제할 수 있습니다.

SampleSyncAdapter 는 저장된 계정 신임 정보를 사용하는 예제입니다.


답변

Android에서 일반적으로 비밀번호를 보호하는 것에 대해 이야기하기 위해 반지에 모자를 넣을 것입니다. Android에서는 장치 바이너리가 손상된 것으로 간주되어야합니다. 이는 직접 사용자가 제어하는 ​​모든 최종 애플리케이션에서 동일합니다. 개념적으로 해커는 바이너리에 필요한 액세스 권한을 사용하여 디 컴파일하고 암호화 된 비밀번호 등을 근절 할 수 있습니다.

따라서 보안이 중요한 관심사 인 경우 두 가지 제안이 있습니다.

1) 실제 비밀번호를 저장하지 마십시오. 부여 된 액세스 토큰을 저장하고 액세스 토큰과 전화기의 서명을 사용하여 세션 서버 측을 인증하십시오. 이것의 장점은 토큰을 제한된 기간으로 만들 수 있고 원래 비밀번호를 손상시키지 않으며 나중에 트래픽과 상관 관계를 유지하는 데 사용할 수있는 좋은 서명을 가지고 있다는 것입니다 (예 : 침입 시도 확인 및 쓸모없는 토큰 렌더링).

2) 2 단계 인증을 활용하십시오. 이것은 더 성 가시고 방해가 될 수 있지만 일부 준수 상황에서는 피할 수 없습니다.