安全准则

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

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

  • Android 应用沙盒,可将您的应用数据和代码执行与其他应用分隔开来。
  • 一个应用框架,可稳健实现常见的安全性功能,例如加密、权限和安全的进程间通信 (IPC)。
  • 类似地址空间布局随机化 (ASLR)不执行 (NX)、ProPolice、safe_iopOpenBSD dlmalloccalloc,以及 Linux mmap_min_addr 等技术,可降低与常见内存管理错误相关的风险。
  • 用户授予的权限,可用来限制对系统功能和用户数据的使用。
  • 应用定义的权限,可针对各个应用分别控制应用数据。

我们建议您熟悉一下本页所述的 Android 安全性最佳实践。遵循这些最佳实践,养成常规编码习惯,有助于避免因疏忽而引发安全问题,防止对用户产生不利的影响。

身份验证

身份验证是许多密钥安全操作的前提条件。若要控制对受保护资源(例如用户数据、应用功能和其他资源)的访问权限,您需向 Android 应用添加身份验证。

您可将应用与 Credential Manager 集成,从而改善用户的身份验证体验。Credential Manager 是一个 Android Jetpack 库,它整合了对大多数主要身份验证方法的 API 支持,包括通行密钥、密码和诸如使用 Google 账号登录之类的联合登录解决方案。

为了进一步增强应用的安全性,不妨考虑添加生物识别验证方法,例如指纹扫描或人脸识别。适合添加生物识别验证的候选应用可能包括财务、医疗保健或身份管理应用。

Android 的自动填充框架可以简化注册和登录过程,从而降低错误率并减少用户遇到的麻烦。自动填充框架与密码管理器集成,允许用户选择可轻松安全地存储和检索的复杂随机密码。

数据存储

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

  • 内部存储空间
  • 外部存储空间
  • content provider
下面几部分内容介绍了与各个方法相关的安全问题。

内部存储空间

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

避免针对 IPC 文件使用已废弃的 MODE_WORLD_WRITEABLEMODE_WORLD_READABLE 模式。这两种模式并不能提供只允许特定应用访问数据的功能,也不会对数据格式进行任何控制。如果您想与其他应用进程共享数据,不妨考虑使用 content provider,它不但可以为其他应用提供读取和写入权限,还能针对各种具体情况授予动态权限。

外部存储空间

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

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

content provider

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

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

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

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

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

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

权限

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

权限请求

您应尽量减少应用请求的权限数。限制对敏感权限的使用可降低不慎误用此类权限的风险,并可提高用户的采用率,同时让您的应用不易遭到攻击者的攻击。通常情况下,如果您的应用无需某项权限也能正常运行,请不要请求该权限。请参阅有关评估应用是否需要声明权限的指南。

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

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

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

权限定义

您应根据您的安全要求定义权限,将所定义权限的数量控制在最低。对于大多数应用来说,它们很少会创建新权限,因为系统定义的权限就能满足大部分的需求。请根据需要使用现有权限执行访问权限检查。

如果您需要创建新权限,请尽量考虑创建 signature 保护级别的权限。“signature”级别权限的内容对用户完全透明开放,而且只有由开发者(执行权限检查的应用的开发者)签名的应用才可访问这些内容。

如果仍需要创建新权限,请使用 <permission> 元素在应用清单中声明该权限。使用新权限的应用可以通过在其清单文件中添加 <uses-permission> 元素来引用该权限。您还可以使用 addPermission() 方法动态添加权限。

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

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

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

网络

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

IP 网络

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

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

有些应用使用 localhost 网络端口处理敏感的 IPC。请勿使用此方法,因为设备上的其他应用也可以访问这些接口。相反,您应使用可通过 Service 等进行身份验证的 Android IPC 机制。绑定到非特定 IP 地址 INADDR_ANY 比使用环回更糟糕,因为它允许您的应用接收来自任何 IP 地址的请求。

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

电话网络

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

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

输入验证

无论应用在哪种平台上运行,输入验证功能不完善都是影响应用的最常见安全问题之一。Android 为此提供了平台级对策,可降低应用出现输入验证问题的可能性。我们建议您尽可能使用这些功能。此外,我们建议使用类型安全的语言,以降低出现输入验证问题的可能性。

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

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

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

如果您无法使用本部分介绍的安全功能,则应确保使用结构合理的数据格式,并验证数据是否符合预期格式。虽然屏蔽特定字符或执行字符替换可能是一种有效的策略,但这些技术在实际操作中很容易出错,因此我们建议尽可能避免使用。

用户数据

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

每当需要访问私密数据时对用户进行身份验证,并使用通行密钥Credential Manager 等新型身份验证方法。请注意,如果您的应用需要访问个人信息,某些管辖区可能会要求您提供隐私权政策,以说明您会如何使用和存储此类数据。建议您遵循安全最佳做法(即尽可能减少对用户数据的访问)以简化合规工作。

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

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

如果需要全局唯一标识符 (GUID),请创建一个较长的具有唯一性的编号并加以存储。请勿使用可能与个人信息关联的电话标识符,例如电话号码或 IMEI。唯一标识符最佳实践页面详细讨论了此主题。

向设备上的日志写入内容时,请务必谨慎小心。在 Android 中,日志是共享资源,拥有 READ_LOGS 权限的所有应用均可访问。即使电话日志数据是临时数据并会在重新启动时清空,不当记录用户信息也可能在无意之中将用户数据泄露给其他应用。除了不记录个人身份信息之外,还要限制正式版应用中的日志使用。为了轻松实现这点,请使用调试标记和自定义 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-store)来指示应用不应缓存特定内容。

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

凭据请求

凭据请求是一种攻击途径。下面这些提示可帮助您提高 Android 应用中的凭据请求的安全性。

尽可能减少凭据泄露

  • 避免不必要的凭据请求。为了让钓鱼式攻击更易辨别并降低其成功率,请尽量降低索要用户凭据的频率。作为替代方法,您可以使用授权令牌并根据需要刷新。仅请求为进行身份验证和授权而需要的最少量凭据信息。
  • 安全地存储凭据。使用 Credential Manager 来启用使用通行密钥的无密码身份验证,或来实现诸如“使用 Google 账号登录”等采用 scheme 的联合登录。如果您必须使用传统的密码身份验证,请不要在设备上存储用户 ID 和密码。您可以使用用户提供的用户名和密码进行初始身份验证,然后使用特定服务专用的短时授权令牌。
  • 限制权限范围。请勿为只需要更窄范围权限的任务请求宽泛的权限。
  • 限制访问令牌。使用短期有效的令牌操作和 API 调用。
  • 限制身份验证速率。快速连续的身份验证或授权请求可能是暴力破解攻击的迹象。将这些速率限制为合理的频率,同时仍能提供实用且人性化的应用体验。

使用安全的身份验证

  • 实现通行密钥。启用通行密钥是一种更安全、更易用的密码升级方式。
  • 添加生物识别。提供允许使用生物识别验证(例如指纹或人脸识别)的选项,以增强安全性。
  • 使用联合身份提供方。Credential Manager 支持联合身份验证提供方,例如使用 Google 账号登录
  • 加密通信:使用 HTTPS 和类似技术来确保您的应用通过网络发送的数据受到保护。

践行安全的账号管理

  • 使用 AccountManager 连接到可供多个应用访问的服务。请使用 AccountManager 类来调用基于云的服务;此外,请勿将密码存储在设备上。
  • 使用 AccountManager 检索 Account 后,请先确认 CREATOR 再传递凭据,以免无意中将凭据传递给错误的应用。
  • 如果凭据仅供您创建的应用使用,那么您可以使用 checkSignatures() 验证访问 AccountManager 的应用。另外,如果只有一个应用使用该凭据,那么您可以使用 KeyStore 存储凭据。

保持警惕

  • 及时更新代码。请务必更新您的源代码(包括任何第三方库和依赖项),以防范最新的漏洞。
  • 监控可疑活动。查找潜在的滥用情况,例如授权滥用模式。
  • 审查您的代码。定期对代码库执行安全检查,以查找潜在的凭据请求问题。

加密

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

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

如果您需要从某个已知网络位置安全地检索文件,使用简单的 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,例如 IntentBinderMessenger,搭配 ServiceBroadcastReceiver。 借助 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 来执行后台服务。

服务可以借助权限保护对该服务进行的各个 IPC 调用。可通过在执行调用实现之前调用 checkCallingPermission() 来做到这一点。我们建议您在清单中使用声明式权限,因为这些权限不容易被忽略。

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

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 安全性概览。此外,即使您使用的是原生代码,也应熟悉各种应用权限。