如何保护Android中的Shared Preferences?

71

SharedPreferences 在 Android 应用程序中通常存储在以下位置:

/data/data/<package name>/shared_prefs/<filename.xml>

拥有root权限的用户可以进入此位置并更改其值。保护它的必要性非常重要。

我们有多少种方式可以加密整个shared_pref's xml文件?

我们都知道我们可以在shared_pref's xml文件中加密和保存数据,但这不是100%安全的,因此需要使用密钥加密整个文件。需要帮助了解各种加密整个xml文件的方法。这是一个一般性的问题,这里讨论的各种加密方法对于所有开发人员在确保应用程序安全方面都可能有所帮助。

12个回答

95

更新答案:

Android在其Jetpack库中发布了一个安全库,其中包含EncryptedSharedPreferences

编辑:从版本v1.1.0开始,您可以支持Lollipop(API 21)及以上版本。

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

1
如果您想加密整个文件,那么您需要自定义偏好设置的实现,并且最好制作自己的解析器。这就是我所做的。我创建了一个基于JNI的新库(C++代码-不易反编译),并使用3KTDES加密保护了整个内容。SharedPreferences的工作原理是众所周知的,即使这一事实使它们不安全。 - Bojan Kseneman
118
Base64不是加密,不要将其用于加密。https://dev59.com/mW855IYBdhLWcg3w7pCg - Klaasvaak
23
所有提到的解决方案都是这样操作的:让我们用锁保护门。但是钥匙放在哪里呢?我们就把钥匙放在门旁边吧。 结论是:所有这些解决方案都没有提供任何安全性 - 它们只是使用模糊来隐藏数据。请注意这一点! - Robert
1
implementation 'androidx.security:security-crypto:1.0.0-alpha02'添加到build.gradle文件中。 - AtomicBoolean
9
如何支持 API 级别 21+? - Sachin Tanpure
显示剩余18条评论

22

1
你有没有实现这个的例子? - GMX
@GMX打开我分享的链接,页面顶部有一个示例。 - Ch3D
2
EncryptedSharedPreferences似乎是一个很好的选择,但有两个缺点:1.它处于alpha通道中 2.此库的最小sdk版本为23。 - Ololoking
3
现在它也支持棒棒糖(21)版本。 - Usman Rana
@UsmanRana,谢谢,我不知道这个。来源:'Lollipop (API Level 21+) 现在得到支持。请注意,AndroidKeyStore 在 API 21 和 22 中不被使用。' - CoolMind
这个比已经沙盒化的应用程序更安全在哪里?EncryptedSharedPreferences使用密钥加密数据,然后将密钥存储在设备上。如果有人可以访问内部应用程序数据,那么什么阻止他们也能够访问加密密钥呢? - user19309143

15

如果您想支持Android 5.0(API级别21)及以上版本

请使用以下实现:

implementation "androidx.security:security-crypto:1.0.0-rc04"

或者从此源获取最新版本。

然后

首先按以下方式创建主密钥:

val masterKey = MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()

创建共享偏好设置后,如下所示:

    val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "secret_shared_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

然后按照平常使用的方式使用它,例如:

with(sharedPreferences.edit()) {
    putString(Values.SP_USER_ID, personId)
    putString(Values.SP_USER_NAME, binding.editTextTextPersonName.text.toString())
    apply()
}

13

您需要处理API 23以下的版本

fun providesSharedPreference(): SharedPreferences {
    var sharedPreferences: SharedPreferences

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        sharedPreferences = EncryptedSharedPreferences.create(
            application,
            Constant.SHARED_PREFERENCE_NAME,
            getMasterKey(),
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )

    } else {
        sharedPreferences =
            application.getSharedPreferences(
                Constant.SHARED_PREFERENCE_NAME,
                Context.MODE_PRIVATE
            )
    }
    return sharedPreferences
}

private fun getMasterKey(): MasterKey {
    return MasterKey.Builder(application)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()
}

1
这对我有用。之前的示例使用过时的API。 - speedynomads
1
旧版SDK的回退是一个不错的选择。 - speedynomads

8

您应该对数据进行加密,并写入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",""));

1
并不比纯文本保存更好,看看这个答案:https://dev59.com/tV8e5IYBdhLWcg3wHXaJ#26077852 - Vitor Hugo Schwaab

7

完整答案(API 级别 23+)。首先,您需要从 androidx 获取加密功能。

implementation "androidx.security:security-crypto:1.0.0-alpha02"

注意:SharedPreferences和EncryptedSharedPreferences之间存在显着的性能差异。你应该注意到EncryptedSharedPreferences.create(...)不太快,因此你应该始终存储一个实例。
然后你必须使用它来检索EncryptedSharedPreferences。
public SharedPreferences getEncryptedSharedPreferences(){
   String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
   SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
       "secret_shared_prefs_file",
       masterKeyAlias,
       context,
       EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
       EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
   );
    return sharedPreferences;
}

您只需使用类似于“标准方式”的首选项即可。保存它:
getEncryptedSharedPreferences().edit()
        .putString("ENCRYPTDATA", text)
        .apply()

检索首选项值。
getEncryptedSharedPreferences().getString("ENCRYPTDATA", "defvalue")

在发布模式下,我遇到了这个错误:“Caused by: java.lang.RuntimeException: Field keySize_ for”。你以前见过这个错误吗? - CanCoder
我从未遇到过这种情况。也许键有大小限制?完整的堆栈跟踪是什么? - Zhar
我在这里发布了一个带有堆栈跟踪的问题:https://dev59.com/57voa4cB1Zd3GeqP11iu - CanCoder
我们能否将现有的sharedPreferences迁移到加密的sharedPreferences? - Usman Rana
如果你的应用程序还没有发布,那么这就要看你是否想保留实际值了。否则你可以手动操作。检查旧的首选项值是否存在,如果存在,使用getEncryptedSharedPreferences().edit().putXX将其迁移到安全值。然后将旧的首选项值标记为已迁移(使用特殊标志或字符串)。当你检索首选项时,检查旧值是否被标记。 - Zhar
你把密钥存储在哪里?否则,当设备被root时,它可能会被获取。 - sandulasanth-7

3
Base64不是加密!不要使用它!是的,“root”用户可以访问这些数据。你可以做的一件事是使用AES来加密这些数据,或者使用单个NoSQL数据库文件并对该文件进行加密。当应用程序打开时,你需要解密数据库并将其用于存储信息,或者独立加密所有文件。
看这里:https://code.tutsplus.com/tutorials/storing-data-securely-on-android--cms-30558

2
使用androidx的security-crypto库(最低API 23),实现双重加密和非加密共享偏好设置的Kotlin示例。
我使用Dagger2将其作为@Singleton注入到需要的地方。
在您的Dagger模块中使用@Name注释来区分SharedPreferences实例,您可以有2个不同的.xml文件(1个加密,1个未加密)用于读取/写入。
在build.gradle中的dependencies中添加:
`implementation "androidx.security:security-crypto:1.0.0-beta01"`
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences

class Prefs(prefsName: String, context: Context) {

    private lateinit var ANDX_SECURITY_KEY_KEYSET: String
    private lateinit var ANDX_SECURITY_VALUE_KEYSET: String
    private lateinit var cntext: Context
    private lateinit var prefName: String

    private lateinit var prefs: SharedPreferences

    constructor(
        prefsName: String,
        context: Context,
        masterKeyAlias: String,
        prefKeyEncryptionScheme: EncryptedSharedPreferences.PrefKeyEncryptionScheme,
        prefValueEncryptionScheme: EncryptedSharedPreferences.PrefValueEncryptionScheme
    ): this(prefsName, context) {
        ANDX_SECURITY_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__"
        ANDX_SECURITY_VALUE_KEYSET =    "__androidx_security_crypto_encrypted_prefs_value_keyset__"
        cntext = context
        prefName = prefsName
        prefs =
            EncryptedSharedPreferences.create(
                prefsName,
                masterKeyAlias,
                context,
                prefKeyEncryptionScheme,
                prefValueEncryptionScheme
            )
    }

    init {
        if (!::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            prefs =
                context.getSharedPreferences(
                    prefsName,
                    Context.MODE_PRIVATE
                )
        }
    }

    companion object {
        const val INVALID_BOOLEAN: Boolean = false
        const val INVALID_FLOAT: Float = -11111111111F
        const val INVALID_INT: Int = -1111111111
        const val INVALID_LONG: Long = -11111111111L
        const val INVALID_STRING: String = "INVALID_STRING"
        val INVALID_STRING_SET: Set<String> = setOf(INVALID_STRING)
    }

    /**
     * OnChangeListener
     * */
    fun registerOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.registerOnSharedPreferenceChangeListener(listener)

    fun unregisterOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.unregisterOnSharedPreferenceChangeListener(listener)

    /**
     * Read Shared Prefs
     * */
    fun contains(key: String): Boolean =
        prefs.contains(key)

    fun getAll(): Map<String, *> =
        prefs.all

    // Returns null if the Boolean value is not in
    //  Shared Preferences
    fun read(key: String): Boolean? =
        if (contains(key)) {
            read(key, INVALID_BOOLEAN)
        } else {
            null
        }

    // Boolean
    fun read(key: String, returnIfInvalid: Boolean): Boolean =
        prefs.getBoolean(key, returnIfInvalid)

    // Float
    fun read(key: String, returnIfInvalid: Float): Float =
        prefs.getFloat(key, returnIfInvalid)

    // Int
    fun read(key: String, returnIfInvalid: Int): Int =
        prefs.getInt(key, returnIfInvalid)

    // Long
    fun read(key: String, returnIfInvalid: Long): Long =
        prefs.getLong(key, returnIfInvalid)

    // Set<String>
    fun read(key: String, returnIfInvalid: Set<String>): Set<String>? =
        prefs.getStringSet(key, returnIfInvalid)

    // String
    fun read(key: String, returnIfInvalid: String): String? =
        prefs.getString(key, returnIfInvalid)

    /**
     * Modify Shared Prefs
     * */
    fun clear() {
        if (::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            val clearTextPrefs = cntext.getSharedPreferences(prefName, Context.MODE_PRIVATE)
            val keyKeyset = clearTextPrefs.getString(ANDX_SECURITY_KEY_KEYSET, INVALID_STRING)
            val valueKeyset = clearTextPrefs.getString(ANDX_SECURITY_VALUE_KEYSET, INVALID_STRING)
            if (keyKeyset != null && keyKeyset != INVALID_STRING
                && valueKeyset != null && valueKeyset != INVALID_STRING) {
                if (!clearTextPrefs.edit().clear().commit()) {
                    clearTextPrefs.edit().clear().apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).apply()
                }
            }
        } else {
            if (!prefs.edit().clear().commit()) {
                prefs.edit().clear().apply()
            }
        }
    }

    fun remove(key: String) {
        if (!prefs.edit().remove(key).commit()) {
            prefs.edit().remove(key).apply()
        }
    }

    // Boolean
    fun write(key: String, value: Boolean) {
        if (!prefs.edit().putBoolean(key, value).commit()) {
            prefs.edit().putBoolean(key, value).apply()
        }
    }

    // Float
    fun write(key: String, value: Float) {
        if (!prefs.edit().putFloat(key, value).commit()) {
            prefs.edit().putFloat(key, value).apply()
        }
    }

    // Int
    fun write(key: String, value: Int) {
        if (!prefs.edit().putInt(key, value).commit()) {
            prefs.edit().putInt(key, value).apply()
        }
    }

    // Long
    fun write(key: String, value: Long) {
        if (!prefs.edit().putLong(key, value).commit()) {
            prefs.edit().putLong(key, value).apply()
        }
    }

    // Set<String>
    fun write(key: String, value: Set<String>) {
        if (!prefs.edit().putStringSet(key, value).commit()) {
            prefs.edit().putStringSet(key, value).apply()
        }
    }

    // String
    fun write(key: String, value: String) {
        if (!prefs.edit().putString(key, value).commit()) {
            prefs.edit().putString(key, value).apply()
        }
    }
}

另一种选项是使用Dagger2进行@Singleton注入的替代方法

AppPrefs.kt

object AppPrefs {
    lateinit var encryptedPrefs: Prefs
    lateinit var prefs: Prefs

    // Add your key strings here...

    fun initEncryptedPrefs(context: Context) {
        encryptedPrefs =
            Prefs(
                "ENCRYPTED_PREFS",
                context,
                MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )
    }

    fun initPrefs(context: Context) {
        prefs = Prefs("PREFS", context)
    }
}

Application.kt

class Application: Application() {
    override fun onCreate() {
        super.onCreate()
        AppPrefs.initEncryptedPrefs(this.applicationContext)
        AppPrefs.initPrefs(this.applicationContext)
    }
}

然后只需从任何地方调用 AppPrefs.prefsAppPrefs.encryptedPrefs

1
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

你会如何将加密的字节转换为可保存在偏好设置 XML 文件中的内容? - behelit
在保存到首选项之前,您应该加密数据,然后保存。 - Mohammad nabil
我有一种感觉,在这个例子中,1)键在内存中被存储的时间比必要的时间长,而且2)键实际上被记录下来了。通常,键被存储在数组中,并在使用后被清除(如用空字节填充)。数组也使得“意外”记录键变得困难,因为Array#toString()只返回数组类型和哈希值。 - Michael Jess

0

如果您担心root权限,基本上您无法防止其读取内部应用程序数据,无论是加密还是其他方式。

具体而言,使用EncryptedSharedPreferences(或类似方法)加密共享首选项不会有任何作用,因为它将加密密钥存储在设备上。Root可以读取加密密钥并解密数据。(Root可以访问任何应用程序的密钥库)。

即使您成功使用root无法访问的密钥安全加密数据,当您解密数据时,数据将在进程的内存中,此时root可以读取它。

如果您担心已经获得root权限的攻击者,那么游戏已经结束了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接