[Android] Android程序中的数据加密存储(AES)
采用AES算法对Android程序中的数据进行加密存储
在开发android程序时,难免会在本地存储一些数据,对于一些敏感数据,我们需要对其进行加密存储,下面记录一下我们采用的一个加密算法。
首先了解一下Android中的四种存储方式。
四种存储方式
1 SharedPreference
SharedPreference中的数据是以key-value的集合需要保存,有些类似于java中的Hashtable。只是SharedPreference中只能存储一些简单的数据类型。同一个Application中可以指定多个sharedPreference,也可以只使用同一个sharedPreference文件。
getSharedPreferences(String name, int mode)方法是根据第一个参数名来区分sharedPreference文件。 getPreferences(int mode) 是返回系统默认的sharedPreference文件。
不管采用哪种方式,sharedPreference存在手机中的位子是在/data/data/{packagename}/shared_prefs/目录下。
2 Internal Storage
Internal Storage(内部存储)并不是将数据存在内存中,内部存储在手机中的位子是/data/data/{packagename}files/,内部存储中的文件只能被自己的应用访问到。当一个应用卸载之后,内部存储中的这些文件也被删除。
获取内部存储的路径可以用
File file = getFilesDir(); Log.i(TAG, "file path:" + file.getAbsolutePath());
结果显示
extFile private path:/data
3 External storage
External storage(外部存储), 我们通常理解的外部存储就是类似于U盘,移动硬盘等。但现在Android设备的配置越来越高, 外部存储已集成到手机内部。外部存储中的文件是可以被用户或者其他应用程序访问和修改。和内部存储不一样,外部存储通常不一定存在,在使用时应该先进行检测。Google给出了这么一段代码:
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
对于外部存储,有两种不同类型的文件。
- public files 文件是可以被自由访问,且文件的数据对其他应用或者用户来说都是由意义的,当应用被卸载之后,文件仍然保留。
- private files 由于private files 也存在于外部存储中, 这种类型的文件只能被其他程序访问,只不过一个应用私有的文件对其他应用其实是没有访问价值的(恶意程序除外)。当应用卸载时,这些文件也会被删除。像我们app下载的一些额外的资源便类似于这种文件。
获取public files的文件路径
File extFilePublic = Environment.getExternalStoragePublicDirectory("test"); Log.i(TAG, "extFile public path:" + extFilePublic.getAbsolutePath());
结果显示
extFile public path:/storage/emulated/0/test
获取private files的文件路径
File extFile = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); Log.i(TAG, "extFile private path:" + extFile.getAbsolutePath());
结果显示
extFile private path:/storage/emulated/0/Android/data/{packageName}/files/Pictures
4 Sqlite
数据存到android自带的sqlite数据库中,这个每个app都会用到。sqlite数据库文件存在内部存储中, 只会被自己的应用程序访问,其存储路径位于/data/data/{packagename}/databases/。关于sqlite的操作,这里不做说明,我的这篇博客里面有具体的操作,可以参看。
通过上面的分析可以看到,对于SharedPreference, inter storage, sqlite中的数据我们都可以用adb 工具查看,都位于/data目录下。所以对于应用中存储的数据,一旦拿到了手机,便可以拿到任何应用的存储数据。因此,对于敏感信息,必须进行加密。
加密
下面以SharedPreference为例,进行数据的加密操作,我们采用的是对称加密算法中的AES算法
public static byte[] encrypt(byte[] key, byte[]data) {
SecretKeySpec sKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher;
byte[] encryptedData = null;
try {
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
encryptedData = cipher.doFinal(data);
} catch (Exception e) {
Log.i(TAG, "encrypt exception" + e.getMessage());
}
return encryptedData;
}
public static byte[] decrypt(byte[] key, byte[]data) {
SecretKeySpec sKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher;
byte [] decryptedData = null;
try {
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
decryptedData = cipher.doFinal(data);
} catch (Exception e) {
Log.i(TAG, "decrypt exception" + e.getMessage());
}
return decryptedData;
}
public static byte[] generateKey(byte[] randomNumberSeed) {
SecretKey sKey = null;
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(randomNumberSeed);
sKey = keyGen.generateKey();
} catch (NoSuchAlgorithmException e) {
Log.i(TAG, "generateKey exception" + e.getMessage());
}
return sKey.getEncoded();
}
//调用的demo
public static void testAES() {
String randomNumberSeed = "aaaa";
String data = "Hello World";
byte [] key = generateKey(randomNumberSeed.getBytes());
byte [] encryptData = encrypt(key, data.getBytes());
String str = Base64.encodeToString(encryptData, Base64.DEFAULT);
Log.i(TAG, "encrypt data:" + str);
byte [] decodeData = Base64.decode(str, Base64.DEFAULT);
Log.i(TAG, "encrypt data:" + new String(decrypt(key, decodeData)));
}
在加密和解密的时候我们都依赖于一个key, 这个key的生成会决定着加密效果。key可以保存在内存中或者以二进制的形式写入到文件,每次当程序启动时,从文件读取到内存中。key的生成,我们可以自定义一些规则,比如当前用户的密码+特定字符串,当前deviceId+特定字符串等等。这个就自定义了。
第三条 获取private files的文件路径 结果显示 是不是不正确呢
@jijiaxin Environment.getDataDirectory()对应的返回的文件路径确实是/data 。但此处关于private files的表达存在错误,应该用context.getExternalFilesDir来获取私有文件。我已更正文章,感谢你的指出。
generateKey的方法里是不是少了keyGen.init方法