fred-ye / summary

my blog
43 stars 9 forks source link

[Android] Android程序中的数据加密存储(AES) #47

Open fred-ye opened 9 years ago

fred-ye commented 9 years ago

采用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的文件路径

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+特定字符串等等。这个就自定义了。

jijiaxin commented 8 years ago

第三条 获取private files的文件路径 结果显示 是不是不正确呢

fred-ye commented 8 years ago

@jijiaxin Environment.getDataDirectory()对应的返回的文件路径确实是/data 。但此处关于private files的表达存在错误,应该用context.getExternalFilesDir来获取私有文件。我已更正文章,感谢你的指出。

jackping commented 7 years ago

generateKey的方法里是不是少了keyGen.init方法