安卓应用安全指南 4.6.1 处理文件 示例代码
安卓应用安全指南 4.6.1 处理文件 示例代码
原书:Android Application Secure Design/Secure Coding Guidebook
译者:飞龙
如上所述,文件原则上应该是私有的。 但是,由于某些原因,有时文件应该由其他应用直接读写。 按照安全角度分类和比较中文件类型如表 4.6-1 所示。 它们根据文件存储位置或其他应用的访问权限分为四类。 下面展示了每个文件类别的示例代码,并在其中添加了每个的解释。
表 4.6-1 按照安全角度的文件类别和比较
文件类别 | 其它应用的访问权限 | 储存位置 | 概述 |
---|---|---|---|
私有文件 | NA | 应用目录中 | (1)只能在应用中读写,(2)可以处理敏感数据,(3)文件原则上应该是这个类型 |
只读公共文件 | 读 | 应用目录中 | (1)其它应用和用户可读,(2)可以处理公开给应用外部的信息 |
读写公共文件 | 读写 | 应用目录中 | (1)其它应用和用户可以读写,(2)从安全和应用设计角度来看,不应该使用 |
外部存储设备(读写文件) | 读写 | 外部存储设备,例如 SD 卡 | (1)没有访问控制,(2)其它应用和用户总是可以读写或删除文件,(3)应该以最小需求使用,(4)可以处理很大的文件 |
4.6.1.1 使用私有文件
这种情况下使用的文件,只能在同一个应用中读取/写入,并且这是使用文件的一种非常安全的方式。 原则上,无论存储在文件中的信息是否是公开的,尽可能使用私有文件,当与其他应用交换必要的信息时,应该使用另一个 Android 系统(内容供应器,服务)来完成。
要点:
1) 文件必须在应用目录中创建。
2) 文件的访问权限必须设置为私有模式,以免其他应用使用。
3) 可以存储敏感信息。
4) 对于存储在文件中的信息,请仔细和安全地处理文件数据。
PrivateFileActivity.java
package org.jssec.android.file.privatefile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class PrivateFileActivity extends Activity {
private TextView mFileView;
private static final String FILE_NAME = "private_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.file);
mFileView = (TextView) findViewById(R.id.file_view);
}
/**
* Create file process
*
* @param view
*/
public void onCreateFileClick(View view) {
FileOutputStream fos = null;
try {
// *** POINT 1 *** Files must be created in application directory.
// *** POINT 2 *** The access privilege of file must be set private mode in order not to be used by other applications.
fos = openFileOutput(FILE_NAME, MODE_PRIVATE);
// *** POINT 3 *** Sensitive information can be stored.
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Not sensotive information (File Activity)¥n").getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PrivateFileActivity", "failed to read file");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
android.util.Log.e("PrivateFileActivity", "failed to close file");
}
}
}
finish();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
fis = openFileInput(FILE_NAME);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PrivateFileActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("PrivateFileActivity", "failed to close file");
}
}
}
}
/**
* Delete file process
*
* @param view
*/
public void onDeleteFileClick(View view) {
File file = new File(this.getFilesDir() + "/" + FILE_NAME);
file.delete();
mFileView.setText(R.string.file_view);
}
}
PrivateUserActivity.java
package org.jssec.android.file.privatefile;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class PrivateUserActivity extends Activity {
private TextView mFileView;
private static final String FILE_NAME = "private_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user);
mFileView = (TextView) findViewById(R.id.file_view);
}
private void callFileActivity() {
Intent intent = new Intent();
intent.setClass(this, PrivateFileActivity.class);
startActivity(intent);
}
/**
* Call file Activity process
*
* @param view
*/
public void onCallFileActivityClick(View view) {
callFileActivity();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
fis = openFileInput(FILE_NAME);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PrivateUserActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("PrivateUserActivity", "failed to close file");
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
FileOutputStream fos = null;
try {
// *** POINT 1 *** Files must be created in application directory.
// *** POINT 2 *** The access privilege of file must be set private mode in order not to be used by other applications.
fos = openFileOutput(FILE_NAME, MODE_APPEND);
// *** POINT 3 *** Sensitive information can be stored.
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Sensitive information (User Activity)¥n").getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PrivateUserActivity", "failed to read file");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
android.util.Log.e("PrivateUserActivity", "failed to close file");
}
}
}
callFileActivity();
}
}
4.6.1.2 使用公共只读文件
这是使用文件向未指定的大量应用公开内容的情况。 如果通过遵循以下几点来实现,那么它也是比较安全的文件使用方法。 请注意,在 API 级别 1 7及更高版本中,不推荐使用MODE_WORLD_READABLE
变量来创建公共文件,并且在 API 级别 24 及更高版本中,会触发安全异常; 因此使用内容供应器的文件共享方法更可取。
要点:
1) 文件必须在应用目录中创建。
2) 文件的访问权限必须设置为其他应用只读。
3) 敏感信息不得存储。
4) 对于要存储在文件中的信息,请仔细和安全地处理文件数据。
PublicFileActivity.java
package org.jssec.android.file.publicfile.readonly;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class PublicFileActivity extends Activity {
private TextView mFileView;
private static final String FILE_NAME = "public_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.file);
mFileView = (TextView) findViewById(R.id.file_view);
}
/**
* Create file process
*
* @param view
*/
public void onCreateFileClick(View view) {
FileOutputStream fos = null;
try {
// *** POINT 1 *** Files must be created in application directory.
// *** POINT 2 *** The access privilege of file must be set to read only to other applications.
// (MODE_WORLD_READABLE is deprecated API Level 17,
// don't use this mode as much as possible and exchange data by using ContentProvider().)
fos = openFileOutput(FILE_NAME, MODE_WORLD_READABLE);
// *** POINT 3 *** Sensitive information must not be stored.
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Not sensitive information (Public File Activity)¥n").getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PublicFileActivity", "failed to read file");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
android.util.Log.e("PublicFileActivity", "failed to close file");
}
}
}
finish();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
fis = openFileInput(FILE_NAME);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("PublicFileActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("PublicFileActivity", "failed to close file");
}
}
}
}
/**
* Delete file process
*
* @param view
*/
public void onDeleteFileClick(View view) {
File file = new File(this.getFilesDir() + "/" + FILE_NAME);
file.delete();
mFileView.setText(R.string.file_view);
}
}
PublicUserActivity.java
package org.jssec.android.file.publicuser.readonly;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class PublicUserActivity extends Activity {
private TextView mFileView;
private static final String TARGET_PACKAGE = "org.jssec.android.file.publicfile.readonly";
private static final String TARGET_CLASS = "org.jssec.android.file.publicfile.readonly.PublicFileActivity";
private static final String FILE_NAME = "public_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user);
mFileView = (TextView) findViewById(R.id.file_view);
}
private void callFileActivity() {
Intent intent = new Intent();
intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
mFileView.setText("(File Activity does not exist)");
}
}
/**
* Call file Activity process
*
* @param view
*/
public void onCallFileActivityClick(View view) {
callFileActivity();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
File file = new File(getFilesPath(FILE_NAME));
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 4 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
android.util.Log.e("PublicUserActivity", "no file");
} catch (IOException e) {
android.util.Log.e("PublicUserActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("PublicUserActivity", "failed to close file");
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
FileOutputStream fos = null;
boolean exception = false;
try {
File file = new File(getFilesPath(FILE_NAME));
// Fail to write in. FileNotFoundException occurs.
fos = new FileOutputStream(file, true);
fos.write(new String("Not sensitive information (Public User Activity)¥n").getBytes());
} catch (IOException e) {
mFileView.setText(e.getMessage());
exception = true;
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
exception = true;
}
}
}
if (!exception)
callFileActivity();
}
private String getFilesPath(String filename) {
String path = "";
try {
Context ctx = createPackageContext(TARGET_PACKAGE,
Context.CONTEXT_RESTRICTED);
File file = new File(ctx.getFilesDir(), filename);
path = file.getPath();
} catch (NameNotFoundException e) {
android.util.Log.e("PublicUserActivity", "no file");
}
return path;
}
}
4.6.1.3 创建公共读写文件
这是一种文件用法,它允许未指定的大量应用的读写访问。
未指定的大量应用可以读写,意思不用多说了。 恶意软件也可以读取和写入,因此数据的可信度和安全性将永远不会得到保证。 另外,即使在没有恶意的情况下,也不能控制文件中的数据格式或写入的时间。 所以这种类型的文件在功能方面几乎不实用。
如上所述,从安全性和应用设计的角度来看,不可能安全地使用读写文件,因此应该避免使用读写文件。
要点:
不要创建允许来自其他应用的读写操作的文件。
4.6.1.4 使用外部存储器(公共读写)文件
将文件存储在 SD 卡等外部存储器中时,就是这种情况。当存储比较庞大的信息(放置从 Web 下载的文件)或者将信息带出到外部时(备份等)时,应该使用它。
对于未指定的大量应用,“外部存储器文件(公共读写)”与“公共读写文件“有相同特性。另外,对于声明使用android.permission.WRITE_EXTERNAL_STORAGE
权限的应用,它和“公共读写文件”具有相同的特性。因此,应尽可能减少“外部存储器(公共读写)文件”的使用。
按照 Android 应用的惯例,备份文件很可能是在外部存储器中创建的。但是,如上所述,外部存储器中的文件存在被其他应用(包括恶意软件)篡改/删除的风险。因此,在输出备份的应用中,为了最小化应用规范或设计方面的风险,一些设计是必要的,例如显示“尽快将备份文件复制到 PC 等安全位置”。
要点:
1) 不得存储敏感信息。
2) 文件必须存储在每个应用的唯一目录中。
3) 对于要存储在文件中的信息,请仔细和安全地处理文件数据。
4) 请求应用的文件写入应该按照规范禁止。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.file.externalfile" >
<!-- declare android.permission.WRITE_EXTERNAL_STORAGE permission to write to the external strage --
>
<!-- In Android 4.4 (API Level 19) and later, the application, which read/write only files in its sp
ecific
directories on external storage media, need not to require the permission and it should declare
the maxSdkVersion -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:allowBackup="false" >
<activity
android:name=".ExternalFileActivity"
android:label="@string/app_name"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
ExternalFileActivity.java
package org.jssec.android.file.externalfile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class ExternalFileActivity extends Activity {
private TextView mFileView;
private static final String TARGET_TYPE = "external";
private static final String FILE_NAME = "external_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.file);
mFileView = (TextView) findViewById(R.id.file_view);
}
/**
* Create file process
*
* @param view
*/
public void onCreateFileClick(View view) {
FileOutputStream fos = null;
try {
// *** POINT 1 *** Sensitive information must not be stored.
// *** POINT 2 *** Files must be stored in the unique directory per application.
File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
fos = new FileOutputStream(file, false);
// *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
fos.write(new String("Non-Sensitive Information(ExternalFileActivity)¥n")
.getBytes());
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("ExternalFileActivity", "failed to read file");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
android.util.Log.e("ExternalFileActivity", "failed to close file");
}
}
}
finish();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("ExternalFileActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("ExternalFileActivity", "failed to close file");
}
}
}
}
/**
* Delete file process
*
* @param view
*/
public void onDeleteFileClick(View view) {
File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
file.delete();
mFileView.setText(R.string.file_view);
}
}
使用的示例代码:
ExternalFileUser.java
package org.jssec.android.file.externaluser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class ExternalUserActivity extends Activity {
private TextView mFileView;
private static final String TARGET_PACKAGE = "org.jssec.android.file.externalfile";
private static final String TARGET_CLASS = "org.jssec.android.file.externalfile.ExternalFileActivity";
private static final String TARGET_TYPE = "external";
private static final String FILE_NAME = "external_file.dat";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user);
mFileView = (TextView) findViewById(R.id.file_view);
}
private void callFileActivity() {
Intent intent = new Intent();
intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
mFileView.setText("(File Activity does not exist)");
}
}
/**
* Call file Activity process
*
* @param view
*/
public void onCallFileActivityClick(View view) {
callFileActivity();
}
/**
* Read file process
*
* @param view
*/
public void onReadFileClick(View view) {
FileInputStream fis = null;
try {
File file = new File(getFilesPath(FILE_NAME));
fis = new FileInputStream(file);
byte[] data = new byte[(int) fis.getChannel().size()];
fis.read(data);
// *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String str = new String(data);
mFileView.setText(str);
} catch (FileNotFoundException e) {
mFileView.setText(R.string.file_view);
} catch (IOException e) {
android.util.Log.e("ExternalUserActivity", "failed to read file");
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
android.util.Log.e("ExternalUserActivity", "failed to close file");
}
}
}
}
/**
* Rewrite file process
*
* @param view
*/
public void onWriteFileClick(View view) {
// *** POINT 4 *** Writing file by the requesting application should be prohibited as the specification.
// Application should be designed supposing malicious application may overwrite or delete file.
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setTitle("POINT 4");
alertDialogBuilder.setMessage("Do not write in calling appllication.");
alertDialogBuilder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callFileActivity();
}
});
alertDialogBuilder.create().show();
}
private String getFilesPath(String filename) {
String path = "";
try {
Context ctx = createPackageContext(TARGET_PACKAGE,
Context.CONTEXT_IGNORE_SECURITY);
File file = new File(ctx.getExternalFilesDir(TARGET_TYPE), filename);
path = file.getPath();
} catch (NameNotFoundException e) {
android.util.Log.e("ExternalUserActivity", "no file");
}
return path;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.file.externaluser" >
<!-- In Android 4.0.3 (API Level 14) and later, the permission for reading external storages
has been defined and the application should decalre that it requires the permission.
In fact in Android 4.4 (API Level 19) and later, that must be declared to read other directories
than the package specific directories. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:allowBackup="false" >
<activity
android:name=".ExternalUserActivity"
android:label="@string/app_name"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>