답변:
일반적으로 SharedPreferences는 환경 설정을 저장하는 가장 좋은 방법이므로 일반적으로 응용 프로그램 및 사용자 설정을 저장하는 것이 좋습니다.
여기서 염려 할 부분은 저축하는 것입니다. 암호는 항상 저장하기 까다로워서 특히 일반 텍스트로 저장하는 것에주의해야합니다. Android 아키텍처는 다른 애플리케이션이 값에 액세스하지 못하도록 보안을 유지하기 위해 애플리케이션의 SharedPreferences가 샌드 박스로 작성되어 있지만 전화에 대한 물리적 액세스는 잠재적으로 값에 대한 액세스를 허용 할 수 있습니다.
가능한 경우 OAuth 와 같은 액세스를 제공하기 위해 협상 된 토큰을 사용하도록 서버를 수정하는 것이 좋습니다. 또는 사소하지는 않지만 일종의 암호화 저장소를 구성해야 할 수도 있습니다. 최소한 암호를 디스크에 쓰기 전에 암호를 암호화하고 있는지 확인하십시오.
MODE_PRIVATE
로컬 저장소에 저장된 데이터를 난독 처리하는 효율성 측면에서 내부 저장소에서 만든 파일과 동일한 작업을 수행하는 것과 동일한 SharedPreferences를 사용하고 있습니까?
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을 리턴하는 것입니다.
괜찮아; 답변이 혼합되어 있기 때문에 오랜 시간이 지났지 만 여기에 몇 가지 일반적인 답변이 있습니다. 나는 이것을 미친 것처럼 연구했고 좋은 대답을 만들기가 어려웠다.
사용자가 장치를 루팅하지 않았다고 가정하면 MODE_PRIVATE 메서드는 일반적으로 안전한 것으로 간주됩니다. 데이터는 파일 시스템의 일부에 일반 프로그램으로 만 액세스 할 수있는 일반 텍스트로 저장됩니다. 이렇게하면 루팅 된 기기에서 다른 앱으로 비밀번호를 쉽게 잡을 수 있습니다. 그런 다음 다시 루팅 된 장치를 지원 하시겠습니까?
AES는 여전히 최고의 암호화입니다. 내가 게시한지 오래 된 경우 새로운 구현을 시작하는 경우 이것을 찾아보십시오. 가장 큰 문제는 "암호화 키로 무엇을해야합니까?"입니다.
이제 "키로 무엇을해야합니까?" 일부. 어려운 부분입니다. 열쇠를 얻는 것이 그렇게 나쁘지 않은 것으로 판명되었습니다. 키 파생 기능을 사용하여 암호를 가져 와서 꽤 안전한 키로 만들 수 있습니다. "PKFDF2로 몇 번의 패스를 사용합니까?"와 같은 문제가 발생하지만 다른 주제입니다.
이상적으로는 장치에서 AES 키를 저장합니다. 서버에서 키를 안전하고 안정적이며 안전하게 검색하는 좋은 방법을 찾아야합니다.
어떤 종류의 로그인 순서 (원격 액세스를 위해 수행 한 원래의 로그인 순서)도 있습니다. 동일한 암호로 키 생성기를 두 번 실행할 수 있습니다. 이것이 작동하는 방식은 새로운 소금과 새로운 안전한 초기화 벡터를 사용하여 키를 두 번 파생시키는 것입니다. 생성 된 비밀번호 중 하나를 디바이스에 저장하고 두 번째 비밀번호를 AES 키로 사용합니다.
로그인하면 로컬 로그인에서 키를 다시 파생시켜 저장된 키와 비교합니다. 이 작업이 완료되면 AES에 파생 키 # 2를 사용합니다.
많은 변형을 할 수 있습니다. 예를 들어, 전체 로그인 순서 대신 빠른 PIN (파생)을 수행 할 수 있습니다. 빠른 PIN은 전체 로그인 시퀀스만큼 안전하지는 않지만 일반 텍스트보다 몇 배 더 안전합니다.
나는 이것이 약간의 괴사라는 것을 알고 있지만 Android AccountManager를 사용해야합니다 . 이 시나리오를 위해 특별히 제작되었습니다. 약간 번거롭지 만 SIM 카드가 변경되면 로컬 자격 증명이 무효화되므로 누군가가 휴대 전화를 스 와이프하고 새로운 SIM을 던지면 자격 증명이 손상되지 않습니다.
또한 사용자는 장치에있는 모든 계정의 저장된 자격 증명을 한 곳에서 쉽고 빠르게 액세스하고 삭제할 수 있습니다.
SampleSyncAdapter 는 저장된 계정 신임 정보를 사용하는 예제입니다.
Android에서 일반적으로 비밀번호를 보호하는 것에 대해 이야기하기 위해 반지에 모자를 넣을 것입니다. Android에서는 장치 바이너리가 손상된 것으로 간주되어야합니다. 이는 직접 사용자가 제어하는 모든 최종 애플리케이션에서 동일합니다. 개념적으로 해커는 바이너리에 필요한 액세스 권한을 사용하여 디 컴파일하고 암호화 된 비밀번호 등을 근절 할 수 있습니다.
따라서 보안이 중요한 관심사 인 경우 두 가지 제안이 있습니다.
1) 실제 비밀번호를 저장하지 마십시오. 부여 된 액세스 토큰을 저장하고 액세스 토큰과 전화기의 서명을 사용하여 세션 서버 측을 인증하십시오. 이것의 장점은 토큰을 제한된 기간으로 만들 수 있고 원래 비밀번호를 손상시키지 않으며 나중에 트래픽과 상관 관계를 유지하는 데 사용할 수있는 좋은 서명을 가지고 있다는 것입니다 (예 : 침입 시도 확인 및 쓸모없는 토큰 렌더링).
2) 2 단계 인증을 활용하십시오. 이것은 더 성 가시고 방해가 될 수 있지만 일부 준수 상황에서는 피할 수 없습니다.
언급 한 기능이 포함 된이 작은 lib를 확인할 수도 있습니다.
https://github.com/kovmarci86/android-secure-preferences
여기에있는 다른 접근 방식과 비슷합니다. 희망은 도움이됩니다 :)
이것은 질문 제목을 기반으로 여기에 도착한 사람들에게 보충 답변이며 암호 저장과 관련된 보안 문제를 처리 할 필요가 없습니다.
사용자 설정은 일반적으로 SharedPreferences
키-값 쌍을 사용하여 Android에 로컬로 저장됩니다 . 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()
, 등
이 답변은 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-encryption-compatibility/ 에 따라 일부 값을 사용하고 있지만 키와 초기화 벡터에 대해
우선 사용자의 데이터는 전화에 저장해서는 안되며 전화에 데이터를 저장 해야하는 경우 앱 개인 데이터로 암호화해야합니다. 사용자 자격 증명의 보안은 응용 프로그램의 우선 순위 여야합니다.
민감한 데이터는 안전하게 저장되거나 전혀 저장되지 않아야합니다. 장치가 손실되거나 맬웨어가 감염된 경우 안전하지 않게 저장된 데이터가 손상 될 수 있습니다.
Android KeyStore를 사용하여 ECB 모드에서 RSA를 사용하여 비밀번호를 암호화 한 다음 SharedPreferences에 저장합니다.
암호를 다시 원할 때 SharedPreferences에서 암호화 된 암호를 읽고 KeyStore를 사용하여 암호를 해독합니다.
이 방법을 사용하면 Android에서 개인 키를 안전하게 저장하고 관리하는 공개 / 개인 키 쌍을 생성 할 수 있습니다.
이를 수행하는 방법에 대한 링크는 다음과 같습니다. Android KeyStore Tutorial
비밀번호를 저장하려면 sqlite, 보안 구심점을 사용해야합니다. 다음은 비밀번호를 저장하는 가장 좋은 예입니다-passwordsafe. 다음은 소스 및 설명에 대한 링크입니다. http://code.google.com/p/android-passwordsafe/