15

我正在使用 SharedPreferences 的自定义子类来加密我在应用程序中保存的设置,类似于这里的第二个响应中所做的:What is the most appropriate way to store user settings in Android application

我必须保存的偏好数量正在增加。在我只是使用自定义视图来更新这些首选项之前,但这会变得很麻烦,我想改用 PreferenceActivity 或 PreferenceFragment。问题是,似乎没有办法让这些类中的任何一个使用我的子类访问我的数据,这意味着它从默认首选项文件中提取的数据将是乱码,因为它没有被解密。

我发现有些人已经创建了 Preference 的自定义实现来加密那里的数据,但我不想这样做,因为数据已经在我的 SharedPreferences 子类中被加密/解密,我想保留它方式。我也一直在查看 PreferenceActivity 和 PreferenceManager 的源代码,但我不确定解决这个问题的最佳方法。

有没有其他人幸运地完成了这样的事情,并对我可以从哪里开始有任何建议?

4

2 回答 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;
    }

}

注意以上是伪代码,会有不同的方法来覆盖


您的 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 来避免)

但是,我认为这并不比 subclassing 安全得多SharedPreferences。这也容易被反编译。最终,如果您想要更强的安全性,您应该考虑替代存储方法。可能是您链接帖子中建议的 OAuth 或 AccountManager。

于 2012-09-20T20:04:19.473 回答
1

这个怎么样:

  • 在 .SO 中存储一个字节 [16]。如果您不使用 .SO,那么就为此目的制作一个。
  • 使用该字节数组加密一个新字节[16],然后 Base64 编码结果。在你的类文件中硬编码。

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

是的,可能有人可以窥视 .SO 并找到字节数组,从而找到您的密钥。但是由于加密的 key2 是 base64 编码的,他需要对其进行解码并使用所述密钥反转加密以提取 key2 字节。到目前为止,这只涉及拆卸应用程序。

  • 当您要存储加密数据时,首先使用 key1 进行 AES 传递,然后使用 Key2 和 IV* 进行 AES/CBC/Padding5 传递
  • 如果您想在每次存储新密码时更改 IV,您可以安全地对 IV 进行 Base64 编码并将其保存在 /data/data 文件夹中。

通过这两个步骤,不再需要拆卸应用程序,因为现在还需要控制运行时以获取加密数据。您不得不说对于存储的密码来说已经足够了。

然后你可以简单地将它存储到 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 但我为您提供了一个解决存储敏感数据问题的可行解决方案:)

于 2012-09-20T11:18:48.340 回答