Как защитить общие настройки Android?
Общее расположение, в котором SharedPreferences
хранятся в приложениях Android:
/data/data/<package name>/shared_prefs/<filename.xml>
Пользователь с привилегиями root может перейти к этому местоположению и может изменить его значения. Сохранение его защиты имеет большое значение.
Сколько способов мы можем зашифровать весь файл shared_pref xml
?
Мы все знаем, что мы можем шифровать и сохранять данные в файле shared_pref xml
, но это не только 100% безопасно, поэтому необходимо зашифровать весь файл с помощью ключа. Нужна помощь в понимании различных способов шифрования всего файла xml
. Это общий вопрос, различные методы шифрования, обсуждаемые в качестве ответов здесь, могут быть полезны всем разработчикам в обеспечении безопасности приложений.
Ответы
Ответ 1
Android выпустил библиотеку безопасности с EncryptedSharedPreferences
в библиотеке crypto
от Jetpack
.
Минимальное API составляет 23 (6. 0+)
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
"secret_shared_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();
https://developer.android.com/reference/kotlin/androidx/security/crypto/EncryptedSharedPreferences.html
Ответ 2
Google выпустил EncryptedSharedPreferences
как часть его androidx, я считаю, что это должен быть предпочтительный способ шифрования настроек.
Видеть https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences
Ответ 3
Вы должны зашифровать свои данные и записать в SharedPreferences. Когда вы хотите получить эти данные, вы должны расшифровать их из SharedPreferences. вам нужен следующий вспомогательный класс для этого
public class Encryption {
private final Builder mBuilder;
private Encryption(Builder builder) {
mBuilder = builder;
}
public static Encryption getDefault(String key, String salt, byte[] iv) {
try {
return Builder.getDefaultBuilder(key, salt, iv).build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
private String encrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
byte[] dataBytes = data.getBytes(mBuilder.getCharsetName());
Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
return Base64.encodeToString(cipher.doFinal(dataBytes), mBuilder.getBase64Mode());
}
public String encryptOrNull(String data) {
try {
return encrypt(data);
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public void encryptAsync(final String data, final Callback callback) {
if (callback == null) return;
new Thread(new Runnable() {
@Override
public void run() {
try {
String encrypt = encrypt(data);
if (encrypt == null) {
callback.onError(new Exception("Encrypt return null, it normally occurs when you send a null data"));
}
callback.onSuccess(encrypt);
} catch (Exception e) {
callback.onError(e);
}
}
}).start();
}
private String decrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
if (data == null) return null;
byte[] dataBytes = Base64.decode(data, mBuilder.getBase64Mode());
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
byte[] dataBytesDecrypted = (cipher.doFinal(dataBytes));
return new String(dataBytesDecrypted);
}
public String decryptOrNull(String data) {
try {
return decrypt(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void decryptAsync(final String data, final Callback callback) {
if (callback == null) return;
new Thread(new Runnable() {
@Override
public void run() {
try {
String decrypt = decrypt(data);
if (decrypt == null) {
callback.onError(new Exception("Decrypt return null, it normally occurs when you send a null data"));
}
callback.onSuccess(decrypt);
} catch (Exception e) {
callback.onError(e);
}
}
}).start();
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING).toCharArray();
}
public interface Callback {
void onSuccess(String result);
void onError(Exception exception);
}
private static class Builder {
private byte[] mIv;
private int mKeyLength;
private int mBase64Mode;
private int mIterationCount;
private String mSalt;
private String mKey;
private String mAlgorithm;
private String mKeyAlgorithm;
private String mCharsetName;
private String mSecretKeyType;
private String mDigestAlgorithm;
private String mSecureRandomAlgorithm;
private SecureRandom mSecureRandom;
private IvParameterSpec mIvParameterSpec;
public static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA1")
.setBase64Mode(Base64.DEFAULT)
.setAlgorithm("AES/CBC/PKCS5Padding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
private Encryption build() throws NoSuchAlgorithmException {
setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
setIvParameterSpec(new IvParameterSpec(getIv()));
return new Encryption(this);
}
private String getCharsetName() {
return mCharsetName;
}
private Builder setCharsetName(String charsetName) {
mCharsetName = charsetName;
return this;
}
private String getAlgorithm() {
return mAlgorithm;
}
private Builder setAlgorithm(String algorithm) {
mAlgorithm = algorithm;
return this;
}
private String getKeyAlgorithm() {
return mKeyAlgorithm;
}
private Builder setKeyAlgorithm(String keyAlgorithm) {
mKeyAlgorithm = keyAlgorithm;
return this;
}
private int getBase64Mode() {
return mBase64Mode;
}
private Builder setBase64Mode(int base64Mode) {
mBase64Mode = base64Mode;
return this;
}
private String getSecretKeyType() {
return mSecretKeyType;
}
private Builder setSecretKeyType(String secretKeyType) {
mSecretKeyType = secretKeyType;
return this;
}
private String getSalt() {
return mSalt;
}
private Builder setSalt(String salt) {
mSalt = salt;
return this;
}
private String getKey() {
return mKey;
}
private Builder setKey(String key) {
mKey = key;
return this;
}
private int getKeyLength() {
return mKeyLength;
}
public Builder setKeyLength(int keyLength) {
mKeyLength = keyLength;
return this;
}
private int getIterationCount() {
return mIterationCount;
}
public Builder setIterationCount(int iterationCount) {
mIterationCount = iterationCount;
return this;
}
private String getSecureRandomAlgorithm() {
return mSecureRandomAlgorithm;
}
public Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
mSecureRandomAlgorithm = secureRandomAlgorithm;
return this;
}
private byte[] getIv() {
return mIv;
}
public Builder setIv(byte[] iv) {
mIv = iv;
return this;
}
private SecureRandom getSecureRandom() {
return mSecureRandom;
}
public Builder setSecureRandom(SecureRandom secureRandom) {
mSecureRandom = secureRandom;
return this;
}
private IvParameterSpec getIvParameterSpec() {
return mIvParameterSpec;
}
public Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
mIvParameterSpec = ivParameterSpec;
return this;
}
private String getDigestAlgorithm() {
return mDigestAlgorithm;
}
public Builder setDigestAlgorithm(String digestAlgorithm) {
mDigestAlgorithm = digestAlgorithm;
return this;
}
}}
то вы можете писать в SharedPreferences путем шифрования ваших данных следующим образом
Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = preferences.edit();
editor.putString("token", encryption.encryptOrNull(userModel.getToken()));
editor.apply()
вы можете, наконец, прочитать данные SharedPreferences следующим образом. Таким образом, конфиденциальная информация будет более безопасной при сохранении на аппаратном уровне телефона
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
String token = encryption.decryptOrNull(preferences.getString("token",""));
Ответ 4
Base64 НЕ шифрует! Не используйте его! Да, пользователи root могут получить доступ к этим данным. Одна вещь, которую вы можете сделать, это использовать AES для шифрования этих данных или использования одного файла базы данных NoSQL и шифрования этого файла. Когда приложение открывается, вы расшифровываете базу данных и используете ее для хранения информации или шифрования всех файлов независимо.
Смотрите здесь: https://code.tutsplus.com/tutorials/storing-data-securely-on-android--cms-30558
Ответ 5
public class NodeCrypto {
private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)
public void doKey(String key)
{
ivspec = new IvParameterSpec(iv.getBytes());
key = padRight(key,16);
Log.d("hi",key);
keyspec = new SecretKeySpec(key.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] encrypt(String text,String key) throws Exception
{
if(text == null || text.length() == 0)
throw new Exception("Empty string");
doKey(key);
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e)
{
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code,String key) throws Exception
{
if(code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
doKey(key);
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
} catch (Exception e)
{
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data)
{
if (data==null)
{
return null;
}
int len = data.length;
String str = "";
for (int i=0; i<len; i++) {
if ((data[i]&0xFF)<16)
str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
else
str = str + java.lang.Integer.toHexString(data[i]&0xFF);
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
private static String padString(String source)
{
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++)
{
source += paddingChar;
}
return source;
}
public static String padRight(String s, int n) {
return String.format("%1$-" + n + "s", s);
}
}
-----------------------------------------------
from your activity or class call encrypt or decrypt method before saving or retriving from SharedPreference