Google 致力于为黑人社区推动种族平等。查看具体举措

安全提示

Android 内置了安全功能,可显著降低应用出现安全问题的频率及其造成的影响。系统已经过精心设计,因此一般情况下,您只需要使用默认系统和文件权限即可打造自己的应用,无需费心在安全性方面做出艰难决策。

您可以借助以下核心安全功能打造安全应用:

  • Android 应用沙盒,可将您的应用数据和代码执行与其他应用分隔开来。
  • 一个应用框架,可稳健实现常见的安全性功能,例如加密、权限和安全 IPC
  • ASLR、NX、ProPolice、safe_iop、OpenBSD dlmalloc、OpenBSD calloc 和 Linux mmap_min_addr 等多项技术,可降低与常见内存管理错误相关的风险。
  • 加密的文件系统,启用后可保护丢失或被盗设备上的数据。
  • 用户授予的权限,可用来限制对系统功能和用户数据的使用。
  • 应用定义的权限,可针对各个应用分别控制应用数据。

我们建议您熟悉一下本文档中所述的 Android 安全性最佳做法。遵循这些最佳做法,养成常规编码习惯,就可以有效减少因疏忽而引发安全问题的几率,防止对用户产生不利的影响。

存储数据

对于 Android 应用而言,最常见的安全顾虑就是其他应用是否能够访问用户保存在设备上的数据。下面介绍了将数据保存在设备上的三种基本方法:

  • 内部存储空间。
  • 外部存储设备。
  • 内容提供程序。
下文介绍了与各个方法相关的安全问题。

使用内部存储空间

默认情况下,您在内部存储空间中创建的文件仅供您的应用访问。Android 实现了这项保护措施,而且这对于大多数应用来说足够了。

一般情况下,您应避免对 IPC 文件使用 MODE_WORLD_WRITEABLEMODE_WORLD_READABLE 模式,因为在这两种模式下,系统不提供针对特定应用限制数据访问的功能,也不会对数据格式进行任何控制。如果您想与其他应用进程共享数据,不妨考虑使用内容提供程序,它不但可以为其他应用提供读取和写入权限,还能针对各种具体情况授予动态权限。

如需为敏感数据提供额外的保护,您可以使用 Security 库对本地文件进行加密。此措施可以保护丢失设备上的数据,而无需进行文件系统加密

使用外部存储设备

外部存储设备(例如 SD 卡)上创建的文件不受任何读取和写入权限的限制。由于用户可以移除外部存储设备中的内容,而且任何应用都可以对这些内容进行修改,因此,请勿使用外部存储设备来存储敏感信息。

如需以更安全的方式读取和写入外部存储设备上的文件,不妨考虑使用 Security 库,它可提供 EncryptedFile 类。

处理来自外部存储设备的数据时,您应执行输入验证,就像处理来自任何不受信任来源的数据时执行输入验证一样。不应在动态加载前将可执行文件或类文件存储在外部存储设备中。如果您的应用确实从外部存储设备中检索可执行文件,应在动态加载前对这些文件执行签名和加密验证。

使用内容提供程序

内容提供程序提供结构化存储机制,可以将内容限制为仅供您自己的应用访问,也可以将内容导出以供其他应用访问。如果您不打算向其他应用授予访问您的 ContentProvider 的权限,请在应用清单中将其标记为 android:exported=false;否则,请将 android:exported 属性设置为 true,以便允许其他应用访问存储的数据。

在创建要导出以供其他应用使用的 ContentProvider 时,您可以指定允许读取和写入的单一权限,也可以针对读取和写入操作分别指定权限。您应仅对需要完成相应任务的应用授予权限。请注意,与其因移除权限而影响现有用户,不如以后提供新功能时再添加权限。

如果您仅使用内容提供程序在您自己的应用之间共享数据,最好将使用的 android:protectionLevel 属性设置为 signature 保护级别。签名权限不需要用户确认,因此,这种方式不仅能提升用户体验,而且在相关应用使用相同的密钥进行签名来访问数据时,还能更好地控制对内容提供程序数据的访问。

内容提供程序还可以通过以下方式提供更细化的访问权限:声明 android:grantUriPermissions 属性,并使用用来启动组件的 Intent 对象中的 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION 标记。使用 <grant-uri-permission> 元素还能进一步限制这些权限的范围。

访问内容提供程序时,请使用参数化的查询方法(例如 query()update()delete()),以免产生来源不受信任的 SQL 注入风险。请注意,如果以组合用户数据的方式构建 selection 参数,然后再将其提交至参数化方法,则使用参数化方法可能不够安全。

请不要误以为提供写入权限的做法很安全。写入权限允许使用 SQL 语句,这使得攻击者可以使用各种 WHERE 子句以及对相关结果进行解析,从而确认某些数据。例如,如果攻击者想要探查通话记录中是否存在某个特定电话号码,只要该号码已经存在,攻击者就可以通过修改其中的一行来获知。如果内容提供程序数据采用可预测的结构,那么授予写入权限相当于同时提供了读取和写入权限。

使用权限

由于 Android 通过沙盒机制管理各个应用,因此应用必须以明确的方式共享资源和数据。为此,应用会通过声明自己需要的权限来获取基本沙盒未提供的额外功能,包括对相机等设备功能的访问权限。

请求权限

您应尽量减少应用请求的权限数量。限制对敏感权限的访问可降低不慎误用此类权限的风险,并可提高用户的采用率,同时让您的应用不易遭到攻击者的攻击。通常情况下,如果您的应用无需某项权限也能正常运行,请不要请求该权限。如果应用缺少某项功能就无法正常运行,请使用 <uses-feature> 元素在清单文件中进行声明。

如果可以采用不需要任何权限的方式设计应用,建议采用这种方式。例如,与其请求访问设备信息来创建唯一标识符,不如为您的应用创建一个 GUID(请参阅处理用户数据的相关部分)。或者,您也可以不将数据存储在外部存储设备(需要请求权限),而将其存储在内部存储空间。

除了请求权限之外,您的应用还可以使用 <permission> 元素来保护对安全性要求较高且会被其他应用访问的 IPC,例如 ContentProvider。一般而言,我们建议您尽量使用访问权限控制,而不使用需要用户确认的权限,因为权限管理对用户来说可能比较复杂。例如,对于同一开发者提供的不同应用之间的 IPC 通信,不妨对权限使用 signature 保护级别

请勿泄露受权限保护的数据。当您的应用通过 IPC 传输数据时可能会出现泄漏,不过,只有当您的应用拥有访问这项数据的权限时,才可能发生数据泄漏。应用 IPC 接口的客户端可能没有相同的数据访问权限。如需详细了解此问题发生的频率及潜在影响,请参阅在 USENIX 上发布的这篇研究论文:Permission Re-Delegation: Attacks and Defenses(《权限重委托:攻击和防御》)。

创建权限

通常情况下,您应在满足安全性要求的前提下尽可能少定义权限。对于大多数应用来说,它们很少会创建新权限,因为系统定义的权限就能满足大部分的需求。请根据需要使用现有权限执行访问权限检查。

如果您必须创建新权限,请尽量考虑创建 signature 保护级别的权限。“signature”级别权限的内容对用户完全透明开放,而且只有由开发者(执行权限检查的应用的开发者)签名的应用才可访问这些内容。如果仍需要创建新权限,则需要使用 <permission> 元素在应用清单中对该权限进行声明。希望使用新权限的应用可以在其各自的清单文件中添加 <uses-permission> 元素来引用相应权限。您还可以使用 addPermission() 方法动态添加权限。

如果您创建了 dangerous 保护级别的权限,情况就会更加复杂,您需要考虑以下几项:

  • 该权限必须包含一个字符串,向用户清楚明确地说明他们需要做出的安全决策。
  • 该权限的字符串必须翻译成多种不同语言。
  • 用户可能会因为权限含糊不清或存在风险而选择不安装应用。
  • 应用可能会在权限创建程序尚未安装的情况下请求权限。

这些情况会给开发者带来巨大的非技术性挑战,同时也会让用户感到困惑,因此我们不建议使用“危险”权限级别。

使用网络

网络交易会涉及到传输那些对用户而言可能比较私密的数据,因此本质上就存在安全风险。用户开始逐渐意识到移动设备存在的隐私泄漏问题,尤其是在通过设备进行网络交易时。因此,请务必对您的应用采取各种最佳做法,以始终确保用户的数据安全。

使用 IP 网络

Android 网络运行机制与其他 Linux 环境差别不大。关键是确保对敏感数据使用合适的协议,如使用 HttpsURLConnection 来保证网络流量安全。您应在服务器支持 HTTPS 的情况下一律使用 HTTPS(而非 HTTP),因为移动设备经常会连接到不安全的网络(例如公共 WLAN 热点)。

您可以使用 SSLSocket 类轻松实现经过身份验证和加密的套接字层通信。考虑到 Android 设备会频繁使用 WLAN 连接到不安全的无线网络,我们强烈建议所有通过网络通信的应用使用安全的网络。

有些应用使用 localhost 网络端口处理敏感的 IPC。您不应使用此方法,因为设备上的其他应用也可以访问这些接口。相反,您应使用可通过 Service 等进行身份验证的 Android IPC 机制。绑定到 INADDR_ANY 比使用回送功能还要糟糕,因为这样一来,您的应用可能会收到任何位置发来的请求。

请勿信任通过 HTTP 或其他非安全协议下载的数据,包括 WebView 中的输入验证以及通过 HTTP 发出的 intent 的任何响应。

使用电话网络

短信协议主要是为用户间通信设计的,并不适合要传输数据的应用。考虑到短信的局限性,要想从网络服务器向用户设备上的应用发送数据消息,您应使用 Google 云消息传递 (GCM) 和 IP 网络。

请注意,短信在网络上和设备上均未经过加密,也没有经过严格的身份验证。而且,所有的短信接收者都应明白,您的应用收到的短信可能来自恶意用户。因此,切勿使用未经身份验证的短信数据执行敏感命令。此外,您还应注意短信可能包含欺骗性内容,也有可能在网络上传输时被拦截。在 Android 设备上,短信会以广播 intent 的形式传输,因此可能会被其他拥有 READ_SMS 权限的应用读取或捕获。

执行输入验证

无论应用在哪种平台上运行,输入验证功能不完善都是影响应用的最常见安全问题之一。Android 为此提供了平台级对策,可降低应用出现输入验证问题的可能性。如果可行,请尽量使用这些功能。另请注意,选择类型安全的语言通常也有助于降低出现输入验证问题的可能性。

如果您使用的是原生代码,那么系统从文件读取、通过网络接收或从 IPC 接收的任何数据都有可能会引发安全问题。最常见的问题包括缓冲区溢出释放后重用差一错误。Android 为此提供了多项技术(例如 ASLRDEP),可以降低这些错误被利用的可能性,但无法解决根本问题。因此,请谨慎处理指针和管理缓冲区,以防止这些漏洞造成破坏。

使用基于字符串的动态语言(例如 JavaScript 和 SQL)也可能因为转义字符和脚本注入而出现输入验证问题。

如果使用提交到 SQL 数据库或内容提供程序的查询中的数据,也可能出现 SQL 注入问题。最好的预防措施是使用参数化查询(请参阅上文内容提供程序部分的相关内容)。将权限限制为只读或只写,也可以降低 SQL 注入引发破坏的可能性。

如果您无法使用上述安全功能,则应确保使用结构合理的数据格式,并验证数据是否符合预期格式。虽然将字符列入黑名单或替换字符是一种有效的策略,但这些技术在实际操作中很容易出错,因此应尽量避免使用。

处理用户数据

通常情况下,确保用户数据安全的最佳做法是尽量避免使用会访问用户敏感数据或个人数据的 API。如果您拥有用户数据的访问权限,并且能够避免存储或传输此类数据,则不要存储或传输此类数据。评估您的应用逻辑能否使用经过哈希算法处理或不可逆的数据格式进行实现。例如,您的应用可使用电子邮件地址的哈希值作为主要密钥,以免传输或存储电子邮件地址。这样可降低在无意之中泄露数据的可能性,还可以降低攻击者尝试利用您的应用搞破坏的可能性。

请注意,如果您的应用会访问密码或用户名等个人信息,部分管辖区可能会要求您提供隐私权政策,以说明您如何使用或存储此类数据。遵循安全最佳做法(即尽可能减少对用户数据的访问)也有助于简化合规工作。

此外,您还应考虑自己的应用是否会在无意之中将个人信息泄露给其他方,例如广告使用的第三方组件或应用使用的第三方服务。如果您不知道某个组件或服务为什么需要个人信息,请不要提供个人信息。通常情况下,减少您的应用对个人信息的访问可降低引发这方面问题的可能性。

如果您的应用需要访问敏感数据,请评估您是需要将其传输到服务器,还是可以在客户端执行相应操作。建议您在客户端运行所有需要使用敏感数据的代码,以避免传输用户数据。此外,请切勿使用权限过于宽松的 IPC、完全没有写入限制的文件或网络套接字,避免在无意之中将用户数据泄露给设备上的其他应用。权限过于宽松的 IPC 是一种造成受权限保护的数据遭泄露的特殊情况(我们已在请求权限部分讨论过)。

如果需要 GUID,请创建一个较长的具有唯一性的编号并加以存储。请勿使用可能与个人信息关联的电话标识符,例如电话号码或 IMEI。如需详细了解此主题,请参阅 Android 开发者博客

向设备上的日志写入内容时,请务必谨慎小心。在 Android 中,日志是共享资源,拥有 READ_LOGS 权限的所有应用均可访问。即使电话日志数据是临时数据并会在重新启动时清空,不当记录用户信息也可能在无意之中将用户数据泄露给其他应用。除了不记录 PII 之外,生产应用还应限制日志的使用。为了轻松实现这点,请使用调试标记和自定义 Log 类(具有可轻松配置的日志记录级别)。

使用 WebView

由于 WebView 使用的网络内容可能包含 HTML 和 JavaScript,因此使用不当可能会引入常见的网络安全问题,例如跨站脚本攻击(JavaScript 注入)。Android 内置了多种机制,可将 WebView 的功能限制为您的应用所需的最低功能,以缩小这些潜在问题的影响范围。

如果您的应用不直接在 WebView 中使用 JavaScript,请勿调用 setJavaScriptEnabled()部分示例代码会使用这种方法,不过您可能需要在实际应用时根据具体情况进行调整。因此,如果不需要使用这种调用方法,请将其移除。默认情况下,WebView 不会执行 JavaScript,因此不可能出现跨站脚本攻击。

addJavaScriptInterface() 允许 JavaScript 调用正常情况下是为 Android 应用预留的操作,因此在使用时请格外小心。如果要使用,请仅将 addJavaScriptInterface() 用于所有输入内容都可信的网页。如果您接受不受信任的输入内容,则不受信任的 JavaScript 可能会调用您应用中的 Android 方法。通常情况下,我们建议您仅将 addJavaScriptInterface() 用于应用 APK 内含的 JavaScript。

如果您的应用通过 WebView 访问敏感数据,您可能需要使用 clearCache() 方法来删除本地存储的所有文件。您还可以使用服务器端标头(例如 no-cache)来指示应用不应缓存特定内容。

如果设备搭载的 Android 平台版本低于 Android 4.4(API 级别 19),则其使用的 webkit 版本存在多个安全问题。如果您的应用在此类设备上运行,解决方法是必须确认 WebView 对象只显示值得信任的内容。为了确保您的应用在 SSL 中不会暴露给潜在的漏洞,请使用可更新的安全 Provider 对象(如更新您的安全提供程序以防范 SSL 攻击中所述)。如果您的应用必须从开放网络渲染内容,请考虑提供您自己的渲染程序,以便使用最新的安全补丁程序让其保持最新状态。

处理凭据

为了提高网上诱骗攻击的可疑性并降低其成功率,请尽量降低要求用户凭据的频率。作为替代方法,您可以使用授权令牌并根据需要刷新。

如果可行,请勿将用户名和密码存储在设备上。您可以使用用户提供的用户名和密码进行初始身份验证,然后使用针对特定服务的短时效授权令牌。

可供多个应用访问的服务应使用 AccountManager 进行访问。如果可行,请使用 AccountManager 类来调用基于云的服务;此外,请勿将密码存储在设备上。

使用 AccountManager 检索 Account 后,请先确认 CREATOR 再传递凭据,以免无意中将凭据传递给错误的应用。

如果凭据仅供您创建的应用使用,那么您可以使用 checkSignature() 验证访问 AccountManager 的应用。另外,如果只有一个应用使用该凭据,那么您可以使用 KeyStore 存储凭据。

使用加密

Android 不仅提供数据隔离机制、支持完整文件系统加密并提供安全通信通道,还提供大量使用加密来保护数据的算法。

一般情况下,您应知道您的软件使用的是哪种 Java 加密架构 (JCA) 安全提供程序。请尝试根据您的具体情况使用已经实现的最高级别的框架。如果适用,请按照 Google 指定的顺序使用 Google 提供的提供程序。

为了更安全地读取和写入本地文件,请使用 Security 库

如果您需要从某个已知网络位置安全地检索文件,使用简单的 HTTPS URI 即可满足需要,无需具备加密知识。如果您需要一个安全通道,不妨考虑使用 HttpsURLConnectionSSLSocket,而无需自行编写协议。如果您使用 SSLSocket,请注意它不会执行主机名验证。请参阅有关直接使用 SSLSocket 的警告

如果您发现需要实现自己的协议,则不应实现您自己的加密算法。请使用现有加密算法,例如 Cipher 类中提供的 AES 和 RSA 实现。此外,您应遵循以下最佳做法:

  • 将 256 位 AES 用于商业用途(如果不可用,请使用 128 位 AES)。
  • 使用 224 位或 256 位公钥大小进行椭圆曲线 (EC) 加密。
  • 知道何时使用 CBC、CTR 或 GCM 分块模式。
  • 在 CTR 模式下避免重复使用 IV/计数器。确保它们是随机加密的。
  • 使用加密时,通过下列函数之一使用 CBC 或 CTR 模式实现完整性:
    • HMAC-SHA1
    • HMAC-SHA-256
    • HMAC-SHA-512
    • GCM 模式

使用安全随机数生成器 SecureRandom 来初始化 KeyGenerator 生成的任意加密密钥。如果使用的密钥不是安全随机数生成器生成的,那么会显著降低算法的强度,而且容易导致离线攻击。

如果您需要存储密钥以供重复使用,请使用 KeyStore 等可以长期存储和检索加密密钥的机制。

使用进程间通信

部分应用会尝试使用传统 Linux 技术(例如网络套接字和共享文件)来实现 IPC。您应改为使用 Android 针对 IPC 提供的系统功能,例如使用 ServiceIntentBinderMessenger,以及 BroadcastReceiver。您可以通过 Android IPC 机制验证连接至 IPC 的应用的身份,并为每种 IPC 机制设置安全政策。

许多安全元素在各种 IPC 机制之间是共享的。如果您的 IPC 机制并不打算让其他应用使用,请在该组件的清单元素(例如 <service> 元素)中将 android:exported 属性设置为 false。对于同一 UID 中包含多项进程的应用,这种做法非常有用;当您在以后的开发过程中决定不以 IPC 的形式提供功能但又不想重新编写代码时,这样做也会有所助益。

如果您的 IPC 可供其他应用访问,您可以使用 <permission> 元素应用安全政策。如果 IPC 是在您自己的不同应用(使用同一密钥签名)之间使用,建议您在 android:protectionLevel 中使用 signature 级别权限。

使用 intent

对于 Activity 和广播接收器,intent 是 Android 中异步 IPC 的首选机制。根据您的应用要求,您可能会对特定的应用组件使用 sendBroadcast()sendOrderedBroadcast() 或显式 intent。出于安全考虑,应首选显式 intent。

注意:如果您使用 intent 绑定到 Service,请使用显式 intent 来确保应用的安全性。使用隐式 intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 intent 调用 bindService(),系统会抛出异常。

请注意,排序后的广播可能会被接收者“占用”,因此它们可能不会传递到所有应用。如果您要发送必须传递到特定接收器的 intent,那么必须使用按名称声明接收器的显式 intent。

Intent 的发送器会验证接收者是否有权通过方法调用来指定非空权限。只有具有该权限的应用才会收到 intent。如果广播 intent 中的数据属于敏感数据,则不妨考虑应用相应权限,以确保恶意应用在没有相应权限的情况下无法注册以接收这些消息。在这些情况下,您还可以考虑直接调用接收器,而不是发起广播。

注意:请勿将 intent 过滤条件视为安全功能。组件可通过显式 intent 调用,但不一定拥有符合 intent 过滤条件的数据。要确认 intent 的格式正确无误,可用于调用的接收器、服务或 Activity,请在 intent 接收器中执行输入验证。

使用服务

Service 通常用于提供其他应用要使用的功能。每个服务类在其清单文件中都必须有相应的 <service> 声明。

默认情况下,服务不会被导出,而且无法由任何其他应用调用。不过,如果您将任何 intent 过滤器添加到服务声明中,那么系统会默认导出该服务。最好是明确声明 android:exported 属性,以确保其行为符合您的需要。您也可以使用 android:permission 属性来保护服务。这样一来,其他应用只有在自己的清单中声明相应的 <uses-permission> 元素,才能启动、停止或绑定到服务。

注意:如果您的应用以 Android 5.0(API 级别 21)或更高版本为目标平台,则应使用 JobScheduler 来执行后台服务。如需详细了解 JobScheduler,请参阅其 API-reference documentation

服务可以先调用 checkCallingPermission(),然后再实现该调用,从而保护针对该服务且拥有相应权限的各个 IPC 调用。您应在清单中使用声明式权限,因为这些权限不容易被忽略。

警告:请勿将客户端权限和服务器权限混淆;请确保被调用应用具有相应权限,并验证您是否为调用应用授予了相同权限。

使用 Binder 和 Messenger 接口

使用 BinderMessenger 是 Android 中 RPC 式 IPC 的首选机制。它们提供了定义完善的接口,可让端点互相进行身份验证(如果需要)。

在设计应用接口时,您应采用无需针对接口进行特定权限检查的方式。应用清单中并未声明 BinderMessenger 对象,因此您无法向这些对象直接应用声明式权限。一般情况下,如果您在 ServiceActivity 中实现了这些对象,那么它们会继承 Service 或 Activity 的应用清单中声明的权限。如果您要创建一个需要身份验证和/或访问控件的接口,则您必须以代码的形式将这些控件明确添加到 BinderMessenger 接口中。

如果您提供的接口确实需要访问控件,请使用 checkCallingPermission() 验证调用方是否具备所需权限。在代表调用方访问服务前,请务必执行此操作,因为您应用的身份会传递到其他接口。如果您调用的是 Service 提供的接口,在无权访问指定服务的情况下,bindService() 调用可能会失败。如果您调用的是自己的应用提供的本地接口,不妨使用 clearCallingIdentity() 方法,此方法可以根据应用的权限屏蔽调用方的权限,从而确保满足内部安全检查的要求。之后,您可以使用 restoreCallingIdentity() 方法恢复调用方的权限。

如需详细了解如何通过服务执行 IPC,请参阅绑定服务

使用广播接收器

BroadcastReceiver 会处理 Intent 发起的异步请求。

默认情况下,接收器会被导出,而且可以由任何其他应用调用。如果您的 BroadcastReceiver 预期供其他应用使用,您可能需要在应用清单中使用 <receiver> 元素向接收器应用安全权限。这样可防止没有相应权限的应用向 BroadcastReceiver 发送 intent。

动态加载代码

我们强烈建议您不要从应用 APK 外部加载代码。这样做不仅会明显加大应用因代码注入或代码篡改产生问题的可能性,还会增加版本管理和应用测试的难度。此外,这还可能导致无法验证应用的行为,因此,某些环境中可能会禁止采用此做法。

如果您的应用会动态加载代码,请务必谨记,运行动态加载的代码需要拥有与应用 APK 相同的安全权限。用户是因为您才决定安装您的应用的,因此他们希望您提供的是在您的应用内运行的代码,包括动态加载的代码。

与动态加载代码相关的主要安全风险与这样的代码需要来自可验证的来源有关。如果这些模块已直接纳入您的 APK 中,那么其他应用就无法对其进行修改;无论代码是原生库代码还是使用 DexClassLoader 加载的类,均是如此。很多应用会尝试从不安全的位置(例如通过未加密的协议从网络上下载)或任何人都可写入内容的位置(例如外部存储设备)加载代码。对于这些位置,网络上的用户将可以修改正在传输中的内容,或者用户设备上的其他应用将可以修改设备上的内容。

虚拟机中的安全性

Dalvik 是 Android 的运行时虚拟机 (VM)。虽然 Dalvik 是专为 Android 而构建的,但是其他虚拟机中存在的很多安全代码问题在 Android 中也会出现。通常情况下,您无需担心有关虚拟机的安全问题。您的应用在安全的沙盒环境中运行,因此系统中的其他进程无法访问您的代码或隐私数据。

如果您希望详细了解虚拟机安全性,请研读有关这方面的一些现有文献。下面是两种比较受欢迎的资源:

本文档重点介绍了 Android 特有或不同于其他虚拟机环境的方面。对于熟悉在其他环境中进行虚拟机编程的开发者,需要注意为 Android 编写应用的以下两大不同之处:

  • 有些虚拟机(例如 JVM 或 .NET 运行时)会充当安全边界,将代码与基本操作系统功能分隔开来。在 Android 上,Dalvik 虚拟机不会充当安全边界,应用沙盒是在操作系统级别实现的,因此 Dalvik 可与同一应用中的本机代码进行互操作,没有安全限制。
  • 鉴于移动设备上的存储空间有限,开发者一般希望构建模块化应用并使用动态类加载。在执行上述操作时,请同时考虑您检索应用逻辑的来源以及您在本地存储应用逻辑的位置。请勿使用从未经验证的来源(例如不安全的网络来源或外部存储设备)加载的动态类,因为此类代码可能遭到篡改,从而执行某些恶意操作。

原生代码的安全性

通常情况下,您应使用 Android SDK 来开发应用,而不要使用 Android NDK 编写原生代码。通过原生代码构建的应用比较复杂、可移植性较差,并且很可能会出现常见的内存损坏错误,例如缓冲区溢出。

Android 使用 Linux 内核构建而成。如果您要使用原生代码,熟悉一下 Linux 开发安全最佳做法会非常有用。本文档没有介绍 Linux 安全做法,不过您可以查看下面这项非常受欢迎的资源:Secure Programming HOWTO - Creating Secure Software(《安全编程指南 - 打造安全软件》)。

Android 与大多数 Linux 环境之间的一个重要区别在于应用沙盒。在 Android 上,所有应用都在应用沙盒中运行,包括那些采用本机代码编写的应用。对于熟悉 Linux 的开发者而言,其本质完全可以汇总成一句话:每个应用都被赋予唯一的 UID 和非常有限的权限。这样就很好理解了。有关详情,请参阅 Android 安全性概览。此外,即使您使用的是原生代码,也应熟悉各种应用权限。