Использование пользовательского подкласса SharedPreferences с PreferenceActivity или PreferenceFragment
Я использую собственный подкласс SharedPreferences для шифрования моих сохраненных настроек в приложении, аналогично тому, что делается во втором ответе здесь: Что является наиболее подходящим способом хранения пользовательских настроек в приложении Android
Число предпочтений, которые мне нужно сохранить, растет. Прежде чем я просто использовал настраиваемый вид для обновления этих настроек, но это станет громоздким, и я хочу использовать PreferenceActivity или PreferenceFragment. Проблема заключается в том, что нет способа заставить любой из этих классов получить доступ к моим данным с помощью моего подкласса, что означает, что данные, которые он извлекает из файла настроек по умолчанию, будут тарабарщиной, поскольку они не были расшифрованы.
Я обнаружил, что некоторые люди создали пользовательские реализации Preference, которые шифруют данные там, но я бы предпочел не делать этого, поскольку данные уже зашифрованы/дешифрованы в моем подклассе SharedPreferences, и я бы хотел Продолжай в том-же духе. Я также просматривал исходный код PreferenceActivity и PreferenceManager, и я не уверен, что это лучший способ приблизиться к этому.
Кто-нибудь еще повезло сделать что-то подобное и предложить какие-либо предложения о том, где я могу начать?
Ответы
Ответ 1
Я думаю, сохраняя ваше шифрование в подклассе SharedPrefs, которое у вас уже есть, вы ограничиваете модульность и разделение проблем.
Итак, я бы предложил пересмотреть подклассификацию самих классов предпочтений (например, CheckBoxPreference) и выполнить ваш расчет там.
В идеале вы также можете использовать какой-то тип композиции или статическую утилиту, так что, хотя вам может потребоваться подкласс каждого используемого вами типа предпочтений, вы можете использовать одно место для выполнения вычислений шифрования/дешифрования. Это также обеспечит вам большую гибкость в будущем, если вам нужно зашифровать или расшифровать некоторые другие данные или изменить API.
Для суб-классификации возможно вы можете сделать это:
Итак, например:
class ListPreferenceCrypt extends ListPreference
{
ListPreferenceCrypt (Context context, AttributeSet attrs) {
super ( context, attrs );
}
ListPreferenceCrypt (Context context) {
super ( context );
}
@Override
public void setValue( String value )
{
//encrypt value
String encryptedVal = MyCryptUtil.encrypt(value);
super.setValue ( encryptedVal );
}
@Override
public String getValue( String key )
{
//decrypt value
String decryptedValue = MyCryptUtil.decrypt(super.getValue ( key ));
return decryptedValue;
}
}
ПРИМЕЧАНИЕ выше, это psuedo-код, были бы разные методы для переопределения
И ваш XML может выглядеть так:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/inline_preferences">
<com.example.myprefs.ListPreferenceCrypt
android:key="listcrypt_preference"
android:title="@string/title_listcrypt_preference"
android:summary="@string/summary_listcrypt_preference" />
</PreferenceCategory>
</PreferenceScreen>
ИЗМЕНИТЬ
Предостережения/Декомпиляция
Как я думал об этом больше, я понял, что одно из предостережений заключается в том, что этот метод не особенно сложно обойти при декомпиляции APK. Это дает полное имя класса переопределенных классов в макетах (хотя этого можно избежать, не используя XML)
Однако я не думаю, что это значительно менее безопасно, чем подклассификация SharedPreferences
. Это тоже подвержено декомпиляции. В конечном счете, если вы хотите повысить безопасность, вы должны рассмотреть альтернативные методы хранения. Возможно, OAuth или AccountManager, как указано в вашем связанном сообщении.
Ответ 2
Как насчет этого:
- Сохраните байт [16] в .SO. Если вы не используете .SO, тогда сделайте это для этой цели.
- Используйте этот массив байтов для шифрования нового байта [16], затем Base64 закодирует результат. Hardcode, что в вашем файле класса.
Теперь, когда вы настроили ключи, позвольте мне объяснить:
Да, возможно, можно заглянуть в .SO и найти массив байтов ergo вашего ключа. Но с зашифрованным ключом2, закодированным base64, ему нужно будет его декодировать и перевернуть шифрование с помощью указанного ключа, чтобы извлечь байты key2. До сих пор это только включало разборку приложения.
- Если вы хотите хранить зашифрованные данные, сначала выполните проход AES с ключом1, затем пропустите AES/CBC/Padding5 с Key2 и IV *
- Вы можете безопасно Base64 закодировать IV и сохранить его в папке /data/data, если вы хотите изменить IV каждый раз, когда будет сохранен новый пароль.
С этими двумя шагами дизассемблирование приложения больше не требуется, поскольку теперь требуется также взять под контроль вашу рабочую среду, чтобы перейти к зашифрованным данным. Что вы должны сказать, достаточно для хранения сохраненного пароля.
Тогда вы можете просто сохранить это в SharedPreferences:) Таким образом, если ваши SharedPreferences будут скомпрометированы, данные все равно будут заблокированы. Я не думаю, что подклассы это действительно правильный подход, но так как вы уже написали свой класс - ну хорошо.
Здесь некоторый код, чтобы еще раз иллюстрировать, что я имею в виду
//use to encrypt key
public static byte[] encryptA(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec sks = getSecretKeySpec(true);
System.err.println("encrypt():\t" + sks.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
return encrypted;
}
//use to encrypt data
public static byte[] encrypt2(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(true);
System.err.println("encrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key1, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
SecretKeySpec key2 = getSecretKeySpec(false);
System.err.println("encrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key2, new IvParameterSpec(getIV()));
byte[] encrypted2 = cipher.doFinal(encrypted);
return encrypted2;//Base64Coder.encode(encrypted2);
}
//use to decrypt data
public static byte[] decrypt2(byte[] message, boolean A) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(false);
System.err.println("decrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key1, new IvParameterSpec(getIV()));
byte[] decrypted = cipher.doFinal(message);
SecretKeySpec key2 = getSecretKeySpec(true);
System.err.println("decrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key2);
byte[] decrypted2 = cipher.doFinal(decrypted);
return decrypted2;
}
//use to decrypt key
public static byte[] decryptKey(String message, byte[] key) throws GeneralSecurityException
{
SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
System.err.println("decryptKey()");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
byte[] decrypted = cipher.doFinal(Base64Coder.decode(message));
return decrypted;
}
//method for fetching keys
private static SecretKeySpec getSecretKeySpec(boolean fromSO) throws NoSuchAlgorithmException, IOException, GeneralSecurityException
{
return new SecretKeySpec(fromSO ? getKeyBytesFromSO() : getKeyBytesFromAssets(), "AES");
}
Как вы думаете?
Я понимаю, что это может быть не в тему, поскольку вы просите об использовании собственных SharedPreferences, но я даю вам рабочее решение проблемы хранения конфиденциальных данных:)