使用自定义的SharedPreferences子类与PreferenceActivity或PreferenceFragment

15
我正在使用自定义的SharedPreferences子类来加密应用程序中保存的设置,类似于这里第二个回复所做的:What is the most appropriate way to store user settings in Android application
我需要保存的首选项数量正在增加。 以前,我只是使用自定义视图来更新这些首选项,但这将变得繁琐,因此我想改用PreferenceActivity或PreferenceFragment。 问题是,似乎没有办法使这两个类使用我的子类访问我的数据,这意味着它从默认首选项文件中提取的数据将是无意义的,因为它没有解密。
我发现有些人创建了自定义的Preference实现来在那里加密数据,但我不想这样做,因为数据已经在我的SharedPreferences子类中加密/解密,我希望保持这种方式。 我还查看了PreferenceActivity和PreferenceManager的源代码,但不确定最好的方法是什么。
是否有其他人成功完成这样的事情,并有任何建议可以指导我从哪里开始?

2
我一直在考虑为了支持我的加密偏好项目而进行这项工作。我想到的唯一解决方案就是基本上要分叉和克隆大部分的偏好UI系统。至少,你需要分叉PreferenceFragment(如果你要支持早于HC的设备,则还需要分叉PreferenceActivity),以及它们直接引用的所有资源。也有可能这就是“全部”所需的,但我不能排除需要分叉和克隆更多的代码的可能性。 - CommonsWare
@JayLamont 抱歉打扰了。您能否提供一个链接,其中包含对首选项进行数据加密的自定义实现? - iceone213
2个回答

2
我认为,如果您将加密保留在已有的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;
    }

}

NOTE the above is psuedo-code, there would be different methods to override


And your XML might look like this:

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

EDIT

Caveats/Decompiling

As I thought about this more, I realized one of the caveats is that this method is not particularly difficult to bypass when decompiling an APK. This does give the full class names of overriden classes in the layouts (though that can be avoided by not using XML)

However, I don't think this is significantly less secure than sub-classing SharedPreferences. That too, is susceptible to decompiling. Ultimately, if you want stronger security, you should consider alternative storage methods. Perhaps OAuth or the AccountManager as suggested in your linked post.


OP写道:“我发现有些人创建了Preference的自定义实现,将数据加密在那里,但我不想这样做,因为数据已经在我的SharedPreferences子类中进行了加密/解密,我希望保持这种方式。” - CommonsWare
没错,但我的建议是(正如我在帖子中所说)重新考虑这个立场 :) - pjco
看起来这将是最好的方法,我的SharedPreferences实现不应该是访问数据的全部入口。感谢您的建议! - Jay Lamont

1

这个怎么样:

  • 在.SO中存储一个byte[16]。如果您不使用.SO,则仅为此目的创建一个。
  • 使用该字节数组对新的byte[16]进行加密,然后对结果进行Base64编码。在您的类文件中硬编码。

现在您已经设置好了密钥,让我解释一下:

是的,潜在的攻击者可能会查看.SO并找到字节数组,从而找到您的密钥。但是由于密钥2经过加密并进行了Base64编码,因此他需要解码并使用该密钥来还原加密以提取key2字节。到目前为止,这仅涉及分解应用程序。

  • 当您想要存储加密数据时,请首先使用key1进行AES传递,然后使用Key2和IV*进行AES/CBC/Padding5传递
  • 您可以安全地对IV进行Base64编码,并像这样将其保存在/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有些偏题,但我会给你一个解决存储敏感数据问题的可行方案 :)

“你认为呢?”——我们已经知道如何在加密的SharedPreferences中存储数据。但我们不知道如何将其与标准的PreferenceScreen UI相结合。我看不出你的解决方案如何解决这个问题。 - CommonsWare
如果它被证明是无用的,我想我会删除它。 - Shark
它不是无用的,但也没有回答问题。 - CommonsWare
个人而言,我会将加密数据存储在常规的SharedPreferences中,但如果您想采用另一种方式,这里有一个备选方案可以使用 :) 我也很感兴趣看看这个如何发展。 - Shark

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