Android 具有内置安全功能,可显著降低应用安全问题的发生频率和影响。该系统旨在让您通常可以使用默认系统和文件权限构建应用,避免在安全性方面做出艰难的决策。
以下核心安全功能可帮助您构建安全的应用程序:
- Android 应用沙盒,可将您的应用数据和代码执行与其他应用隔离。
- 具有常见安全功能(如加密、权限和安全进程间通信 (IPC))的强大实现的应用程序框架。
- 地址空间布局随机化 (ASLR)、无执行 (NX)、ProPolice、safe_iop、OpenBSD
dlmalloc
和calloc
以及 Linuxmmap_min_addr
等技术,可缓解与常见内存管理错误相关的风险。 - 用户授予的权限,用于限制对系统功能和用户数据的访问。
- 应用定义的权限,用于按应用控制应用程序数据。
熟悉本页面上的 Android 安全最佳实践非常重要。将这些实践作为一般的编码习惯有助于您避免无意中引入对用户产生不利影响的安全问题。
身份验证
身份验证是许多关键安全操作的先决条件。要控制对受保护资产(如用户数据、应用功能和其他资源)的访问,您需要在 Android 应用中添加身份验证。
通过将您的应用与凭据管理器集成,您可以改善用户的身份验证体验。凭据管理器是一个 Android Jetpack 库,它统一了对大多数主要身份验证方法的 API 支持,包括通行密钥、密码和联合登录解决方案,例如通过 Google 登录。
为了进一步增强应用的安全性,请考虑添加生物识别身份验证方法,例如指纹扫描或面部识别。添加生物识别身份验证的良好候选应用可能包括金融、医疗保健或身份管理应用。
Android 的自动填充框架可以简化注册和登录过程,减少错误率和用户摩擦。自动填充与密码管理器集成,允许用户选择复杂、随机的密码,这些密码可以轻松安全地存储和检索。
应用完整性
Play Integrity API 可帮助您检查交互和服务器请求是否来自运行在真实 Android 设备的真实应用二进制文件。通过检测潜在的风险和欺诈性交互(例如来自篡改的应用版本和不可信的环境),您应用的后端服务器可以采取适当的措施来防止攻击和减少滥用。
数据存储
Android 应用最常见的安全问题是您在设备上保存的数据是否可供其他应用访问。在设备上保存数据有三种基本方式:
- 内部存储
- 外部存储
- 内容提供程序
以下各节描述了每种方法相关的安全问题。
内部存储
默认情况下,您在内部存储上创建的文件只能由您的应用访问。Android 实现了这种保护,对于大多数应用程序来说已经足够了。
避免使用已弃用的 MODE_WORLD_WRITEABLE
和 MODE_WORLD_READABLE
模式用于 IPC 文件。它们无法将数据访问限制到特定应用程序,也无法提供数据格式的任何控制。如果您想与其他应用进程共享数据,请考虑改用内容提供程序,它提供对其他应用的读写权限,并可以根据具体情况动态授予权限。
外部存储
在外部存储(例如 SD 卡)上创建的文件是全局可读写的。由于外部存储可能被用户移除,也可能被任何应用程序修改,因此只能使用外部存储来存储非敏感信息。
在处理来自外部存储的数据时,请像处理来自任何不受信任的源的数据一样执行输入验证。在动态加载之前,不要将可执行文件或类文件存储在外部存储上。如果您的应用确实从外部存储检索可执行文件,请确保这些文件在动态加载之前已签名并经过加密验证。
内容提供程序
内容提供程序提供了一种结构化存储机制,可以将其限制为仅您的应用程序使用,或者导出以允许其他应用程序访问。如果您不打算向其他应用程序提供对您的 ContentProvider
的访问权限,请在应用程序清单中将其标记为 android:exported=false
。否则,将 android:exported
属性设置为 true
以允许其他应用访问存储的数据。
当创建一个导出供其他应用程序使用的 ContentProvider
时,您可以为读写指定一个单一的权限,或者您可以为读写指定不同的权限。将您的权限限制在完成手头任务所需的范围内。请记住,以后添加权限以暴露新功能通常比取消权限并影响现有用户更容易。
如果您仅在自己的应用之间共享数据时使用内容提供程序,我们建议将 android:protectionLevel
属性设置为 signature
保护级别。签名权限不需要用户确认,因此当访问数据的应用与签名密钥相同时,它们可以提供更好的用户体验和对内容提供程序数据更受控制的访问。
内容提供程序还可以通过声明 android:grantUriPermissions
属性,并在激活组件的 Intent
对象中使用 FLAG_GRANT_READ_URI_PERMISSION
和 FLAG_GRANT_WRITE_URI_PERMISSION
标志来提供更细粒度的访问。这些权限的范围可以通过 <grant-uri-permission>
元素进一步限制。
访问内容提供程序时,请使用参数化查询方法,例如 query
、update
和 delete()
,以避免来自不可信来源的潜在 SQL 注入。请注意,如果在提交给方法之前通过连接用户数据构建 selection
参数,则使用参数化方法是不够的。
不要对写入权限抱有错误的安全感。写入权限允许使用 SQL 语句,通过巧妙的 WHERE
子句和解析结果来确认某些数据。例如,攻击者可能会通过仅在特定电话号码已存在时修改行来探测通话记录中是否存在该号码。如果内容提供程序数据具有可预测的结构,则写入权限可能等同于提供读写权限。
权限
由于 Android 将应用程序彼此沙盒化,应用程序必须显式共享资源和数据。它们通过声明它们需要额外功能(基本沙盒未提供)的权限来完成此操作,包括访问相机等设备功能。
权限请求
最小化您的应用请求的权限数量。限制对敏感权限的访问可以降低无意中滥用这些权限的风险,提高用户接受度,并使您的应用对攻击者的漏洞更少。通常,如果您的应用不需要某个权限即可运行,请不要请求它。请参阅关于评估您的应用是否需要声明权限的指南。
如果可能,以不需要任何权限的方式设计您的应用程序。例如,与其请求设备信息访问权限以创建唯一标识符,不如为您的应用程序创建一个 UUID。(在关于用户数据的部分了解更多)。或者,与其使用外部存储(需要权限),不如将数据存储在内部存储中。
除了请求权限之外,您的应用程序还可以使用 <permission>
元素来保护对其他应用程序公开的安全敏感型 IPC,例如 ContentProvider
。通常,我们建议尽可能使用用户确认权限以外的访问控制,因为权限可能会让用户感到困惑。例如,对于由单个开发者提供的应用程序之间的 IPC 通信,考虑在权限上使用签名保护级别。
不要泄露受权限保护的数据。当您的应用通过 IPC 暴露数据,而这些数据仅因您的应用拥有访问权限而可用时,就会发生这种情况。您的应用 IPC 接口的客户端可能没有相同的数据访问权限。有关此问题的频率和潜在影响的更多详细信息,请参阅 USENIX 发表的研究论文《权限再委托:攻击与防御》。
权限定义
定义满足您安全要求的最少权限集。对于大多数应用程序来说,创建新权限相对不常见,因为系统定义的权限涵盖了许多情况。在适当的情况下,使用现有权限执行访问检查。
如果您需要新权限,请考虑是否可以使用签名保护级别来完成任务。签名权限对用户透明,并且只允许由与执行权限检查的应用程序相同的开发者签名的应用程序访问。
如果仍然需要创建新权限,请使用 <permission>
元素在应用清单中声明它。使用新权限的应用可以通过在清单文件中添加 <uses-permission>
元素来引用它。您还可以使用 addPermission()
方法动态添加权限。
如果您创建了一个具有危险保护级别的权限,则需要考虑许多复杂性:
- 权限必须有一个字符串,以简洁的方式向用户表达他们需要做出的安全决策。
- 权限字符串必须本地化为多种不同的语言。
- 用户可能因为权限令人困惑或被认为有风险而选择不安装应用程序。
- 当权限的创建者尚未安装时,应用程序可能会请求该权限。
这些都会给您作为开发者带来重大的非技术挑战,同时也会让您的用户感到困惑,这就是我们不鼓励使用危险权限级别的原因。
网络
网络事务天生具有安全风险,因为它们涉及传输可能对用户来说是私密的数据。人们越来越意识到移动设备的隐私问题,尤其是在设备执行网络事务时,因此您的应用程序始终实施所有最佳实践以确保用户数据安全非常重要。
IP 网络
Android 上的网络与其它 Linux 环境没有显著差异。关键的考虑是确保敏感数据使用适当的协议,例如用于安全网络流量的 HttpsURLConnection
。只要服务器支持 HTTPS,就始终使用 HTTPS 而不是 HTTP,因为移动设备经常连接到不安全的网络,例如公共 Wi-Fi 热点。
使用 SSLSocket
类可以轻松实现经过身份验证的加密套接字级别通信。鉴于 Android 设备使用 Wi-Fi 连接不安全无线网络的频率,强烈建议所有通过网络通信的应用程序使用安全网络。
一些应用程序使用 localhost 网络端口来处理敏感 IPC。请勿使用此方法,因为这些接口可供设备上的其他应用程序访问。相反,请使用 Android IPC 机制(其中可以进行身份验证),例如与 Service
结合使用。绑定到非特定 IP 地址 INADDR_ANY
比使用回环更糟糕,因为它允许您的应用程序接收来自任何 IP 地址的请求。
确保您不信任从 HTTP 或其他不安全协议下载的数据。这包括 WebView
中的输入验证以及对 HTTP 发出的任何 Intent 的响应。
电话网络
短消息服务 (SMS) 协议主要用于用户到用户的通信,不适合需要传输数据的应用程序。由于 SMS 的限制,我们建议使用 Firebase Cloud Messaging (FCM) 和 IP 网络将数据消息从 Web 服务器发送到用户设备上的应用程序。
请注意,短信在网络或设备上既未加密也未强认证。特别是,任何短信接收者都应预期恶意用户可能已向您的应用程序发送了短信。不要依赖未经认证的短信数据来执行敏感命令。此外,请注意短信可能在网络上受到欺骗和/或拦截。在 Android 设备本身上,短信以广播意图的形式传输,因此它们可以被其他具有 READ_SMS
权限的应用程序读取或捕获。
输入验证
输入验证不足是影响应用程序最常见的安全问题之一,无论它们运行在哪个平台上。Android 拥有平台级对策,可以减少应用程序暴露于输入验证问题,我们建议您尽可能使用这些功能。此外,我们建议使用类型安全语言来减少输入验证问题的可能性。
如果您正在使用原生代码,从文件读取的任何数据、通过网络接收的数据或从 IPC 接收的数据都可能引入安全问题。最常见的问题是缓冲区溢出、使用已释放内存和差一错误。Android 提供了许多技术,如 ASLR 和数据执行保护 (DEP),这些技术可以减少这些错误的利用性,但它们不能解决根本问题。您可以通过仔细处理指针和管理缓冲区来防止这些漏洞。
动态的、基于字符串的语言(如 JavaScript 和 SQL)也可能由于转义字符和脚本注入而出现输入验证问题。
如果您在提交到 SQL 数据库或内容提供程序的查询中使用数据,SQL 注入可能是一个问题。最好的防御是使用参数化查询,如内容提供程序一节所述。将权限限制为只读或只写也可以减少与 SQL 注入相关的潜在危害。
如果您无法使用本节讨论的安全功能,请务必使用结构良好的数据格式并验证数据是否符合预期格式。虽然阻止特定字符或执行字符替换可能是一种有效的策略,但这些技术在实践中容易出错,我们建议尽可能避免使用它们。
用户数据
用户数据安全的最佳方法是尽量减少使用访问敏感或个人信息的 API。如果您有权访问用户数据,如果可以,请避免存储或传输它。考虑您的应用程序逻辑是否可以使用数据的哈希或不可逆形式实现。例如,您的应用程序可能使用电子邮件地址的哈希作为主键,以避免传输或存储电子邮件地址。这减少了无意中暴露数据的机会,也减少了攻击者试图利用您的应用程序的机会。
每当需要访问私人数据时,请对您的用户进行身份验证,并使用现代身份验证方法,例如通行密钥和凭据管理器。如果您的应用需要访问个人信息,请记住某些司法管辖区可能要求您提供隐私政策,解释您对该数据的使用和存储。遵循最小化用户数据访问的安全最佳实践以简化合规性。
此外,还要考虑您的应用程序是否会无意中将个人信息暴露给其他方,例如用于广告的第三方组件或您的应用程序使用的第三方服务。如果您不知道某个组件或服务为何需要个人信息,请不要提供。总的来说,减少您的应用程序对个人信息的访问可以减少这方面问题的潜在发生。
如果您的应用需要访问敏感数据,请评估您是否需要将其传输到服务器,或者是否可以在客户端上运行该操作。考虑在客户端上运行任何使用敏感数据的代码,以避免传输用户数据。此外,请确保您不会通过权限过度宽松的 IPC、全局可写文件或网络套接字,无意中将用户数据暴露给设备上的其他应用程序。权限过度宽松的 IPC 是泄露受权限保护数据的一种特殊情况,在权限请求部分中讨论。
如果需要全局唯一标识符 (GUID),请创建一个大而唯一的数字并将其存储起来。不要使用电话号码或 IMEI 等可能与个人信息相关的电话标识符。此主题在关于唯一标识符最佳实践的页面中进行了更详细的讨论。
在写入设备日志时要小心。在 Android 上,日志是共享资源,可供具有 READ_LOGS
权限的应用程序访问。尽管电话日志数据是临时的,并在重启时擦除,但对用户信息的不当日志记录可能会无意中将用户数据泄露给其他应用程序。除了不记录个人身份信息 (PII) 外,还要限制生产应用程序中的日志使用。为了轻松实现这一点,请使用调试标志和具有易于配置的日志记录级别的自定义 Log
类。
WebView
由于 WebView
消耗可能包含 HTML 和 JavaScript 的网页内容,因此不当使用可能会引入常见的 Web 安全问题,例如跨站脚本(JavaScript 注入)。Android 包含多种机制,通过将 WebView
的功能限制在应用程序所需的最低限度来减少这些潜在问题的范围。
如果您的应用没有直接在 WebView
中使用 JavaScript,请不要调用 setJavaScriptEnabled
。有些示例代码使用此方法;如果您在生产应用程序中重新利用使用此方法的示例代码,如果不需要,请删除该方法调用。默认情况下,WebView
不执行 JavaScript,因此无法进行跨站脚本攻击。
使用 addJavaScriptInterface()
时要特别小心,因为它允许 JavaScript 调用通常保留给 Android 应用程序的操作。如果您使用它,请仅将 addJavaScriptInterface()
暴露给所有输入都受信任的网页。如果允许不受信任的输入,不受信任的 JavaScript 可能会在您的应用中调用 Android 方法。一般来说,我们建议仅将 addJavaScriptInterface()
暴露给包含在您的应用程序 APK 中的 JavaScript。
如果您的应用程序使用 WebView
访问敏感数据,请考虑使用 clearCache()
方法删除任何本地存储的文件。您还可以使用服务器端标头(例如 no-store
)来指示应用程序不应缓存特定内容。
运行 Android 4.4 (API level 19) 之前平台的设备使用的 webkit
版本存在一些安全问题。作为一种变通方法,如果您的应用在这些设备上运行,它必须确认 WebView
对象只显示受信任的内容。为确保您的应用不会暴露于 SSL 的潜在漏洞,请按照更新您的安全提供程序以防范 SSL 攻击中的描述,使用可更新的安全Provider
对象。如果您的应用必须渲染来自开放网络的内容,请考虑提供您自己的渲染器,以便您可以将其与最新的安全补丁保持同步。
凭据请求
凭据请求是攻击的载体。以下是一些提示,可帮助您在 Android 应用中更安全地进行凭据请求。
最小化凭据暴露
- 避免不必要的凭据请求。为了让网络钓鱼攻击更加显眼且不易成功,请尽量减少询问用户凭据的频率。相反,使用授权令牌并刷新它。仅请求身份验证和授权所需的最低凭据信息。
- 安全存储凭据。使用凭据管理器启用使用通行密钥的无密码身份验证,或使用 Google 登录等方案实现联合登录。如果您必须使用传统密码身份验证,请不要在设备上存储用户 ID 和密码。相反,使用用户提供的用户名和密码执行初始身份验证,然后使用短期、特定于服务的授权令牌。
- 限制权限范围。对于只需要更窄范围的任务,不要请求广泛的权限。
- 限制访问令牌。将短期令牌操作和 API 调用。
- 限制身份验证速率。快速、连续的身份验证或授权请求可能是暴力攻击的迹象。将这些速率限制在合理的频率,同时仍允许功能齐全且用户友好的应用体验。
使用安全身份验证
- 实施通行密钥。启用通行密钥作为密码更安全、更用户友好的升级。
- 添加生物识别。提供使用生物识别身份验证(例如指纹或面部识别)的能力,以增强安全性。
- 使用联合身份提供商。凭据管理器支持联合身份验证提供商,例如通过 Google 登录。
- 加密通信 使用 HTTPS 和类似技术来确保您的应用通过网络发送的数据受到保护。
实践安全帐户管理
- 使用
AccountManager
连接到多个应用程序可访问的服务。使用AccountManager
类调用基于云的服务,并且不要在设备上存储密码。 - 在使用
AccountManager
检索Account
后,在传入任何凭据之前使用CREATOR
,这样您就不会无意中将凭据传递给错误的应用程序。 - 如果凭据仅由您创建的应用程序使用,您可以使用
checkSignatures
验证访问AccountManager
的应用程序。或者,如果只有一个应用程序使用凭据,您可以使用KeyStore
进行存储。
保持警惕
- 保持代码最新。务必更新您的源代码,包括任何第三方库和依赖项,以防范最新的漏洞。
- 监控可疑活动。查找潜在的滥用行为,例如权限滥用模式。
- 审计您的代码。定期对您的代码库执行安全检查,以查找潜在的凭据请求问题。
API 密钥管理
API 密钥是许多 Android 应用程序的关键组件,使它们能够访问外部服务并执行基本功能,例如连接到地图服务、身份验证和天气服务。但是,暴露这些敏感密钥可能会导致严重的后果,包括数据泄露、未经授权的访问和财务损失。为了防止这种情况发生,开发人员应在整个开发过程中实施安全的 API 密钥处理策略。
为了保护服务免受滥用,API 密钥必须受到严密保护。要确保应用与使用 API 密钥的服务之间的连接安全,您需要确保对 API 的访问安全。当您的应用被编译并且您的应用源代码包含 API 密钥时,攻击者有可能反编译应用并找到这些资源。
本节适用于两类 Android 开发者:与基础设施团队合作进行持续交付管道的开发者,以及在 Play 商店中部署独立应用的开发者。本节概述了处理 API 密钥的最佳实践,以便您的应用可以安全地与服务通信。
生成与存储
开发人员应将 API 密钥存储视为数据保护和用户隐私的关键组成部分,并采用纵深防御方法。
强大的密钥存储
为了获得最佳的密钥管理安全性,请使用Android 密钥库,并使用诸如Tink Java之类的强大工具对存储的密钥进行加密。
源代码控制排除
切勿将 API 密钥提交到您的源代码存储库。将 API 密钥添加到源代码中存在将密钥暴露给公共存储库、共享代码示例和意外共享文件的风险。相反,请使用 Gradle 插件(例如 secrets-gradle-plugin)在您的项目中处理 API 密钥。
特定于环境的密钥
如果可能,为开发、测试和生产环境使用单独的 API 密钥。使用特定于环境的密钥可以隔离每个环境,从而降低暴露生产数据的风险,并允许您禁用受损密钥而不会影响生产环境。
使用和访问控制
安全的 API 密钥实践对于保护您的 API 和用户至关重要。以下是如何为您的密钥准备最佳安全性:
- 为每个应用生成唯一的密钥:为每个应用使用单独的 API 密钥,以帮助识别和隔离受损访问。
- 实施 IP 限制:如果可能,将 API 密钥使用限制在特定的 IP 地址或范围。
- 限制移动应用密钥使用:通过将 API 密钥与密钥捆绑或使用应用证书,将 API 密钥使用限制在特定的移动应用。
- 记录和监控可疑活动:实施 API 使用日志和监控机制,以检测可疑活动并防止潜在的滥用。
注意:您的服务应提供将密钥限制到特定包或平台的功能。例如,Google 地图 API 通过包名和签名密钥限制密钥访问。
OAuth 2.0 提供了一个用于授权访问资源的框架。它定义了客户端和服务器应如何交互的标准,并允许安全授权。您可以使用 OAuth 2.0 将 API 密钥的使用限制到特定客户端,并定义访问范围,以便每个 API 密钥只拥有其预期目的所需的最低访问级别。
密钥轮换和过期
为了降低通过未发现的 API 漏洞进行未经授权访问的风险,定期轮换 API 密钥非常重要。ISO 27001 标准定义了密钥轮换频率的合规性框架。在大多数情况下,90 天到 6 个月之间的密钥轮换周期应该足够了。实施强大的密钥管理系统可以帮助您简化这些流程,提高密钥轮换和过期需求的效率。
一般最佳实践
- 使用 SSL/HTTPS:始终使用 HTTPS 通信来加密您的 API 请求。
- 证书锁定:为了增加额外的安全层,您可以考虑实施证书锁定,以检查哪些证书被认为是有效的。
- 验证和清理用户输入:验证和清理用户输入,以防止可能暴露 API 密钥的注入攻击。
- 遵循安全最佳实践:在您的开发过程中实施通用的安全最佳实践,包括安全编码技术、代码审查和漏洞扫描。
- 保持更新:及时了解最新的安全威胁和 API 密钥管理最佳实践。
- SDK 最新:确保您的 SDK 和库已更新到最新版本。
密码学
除了提供数据隔离、支持全文件系统加密和提供安全通信通道外,Android 还提供了大量算法来使用密码学保护数据。
了解您的软件使用了哪些 Java 密码体系结构 (JCA) 安全提供程序。尝试使用能够支持您用例的现有框架实现的最高级别。如果适用,请按照 Google 指定的顺序使用 Google 提供的提供程序。
如果您需要从已知网络位置安全地检索文件,一个简单的 HTTPS URI 可能就足够了,并且不需要任何密码学知识。如果您需要安全隧道,请考虑使用 HttpsURLConnection
或 SSLSocket
,而不是编写自己的协议。如果您使用 SSLSocket
,请注意它不执行主机名验证。请参阅关于直接使用 SSLSocket
的警告。
如果您发现需要实现自己的协议,请不要实现自己的加密算法。使用现有的加密算法,例如 Cipher
类中提供的 AES 和 RSA 实现。此外,请遵循以下最佳实践:
- 商业用途使用 256 位 AES。(如果不可用,使用 128 位 AES。)
- 椭圆曲线 (EC) 加密使用 224 位或 256 位公钥大小。
- 了解何时使用 CBC、CTR 或 GCM 块模式。
- 在 CTR 模式中避免 IV/计数器重用。确保它们是加密随机的。
- 使用加密时,使用 CBC 或 CTR 模式结合以下任一函数实现完整性:
- HMAC-SHA1
- HMAC-SHA-256
- HMAC-SHA-512
- GCM 模式
使用安全随机数生成器 SecureRandom
来初始化由 KeyGenerator
生成的任何加密密钥。使用未由安全随机数生成器生成的密钥会显著削弱算法的强度,并可能允许离线攻击。
如果您需要存储密钥以重复使用,请使用 KeyStore
等机制,它提供加密密钥的长期存储和检索。
进程间通信
有些应用尝试使用传统的 Linux 技术(如网络套接字和共享文件)来实现 IPC。但是,我们建议您改用 Android 系统功能进行 IPC,例如 Intent
、Binder
或与 Service
结合使用的 Messenger
,以及 BroadcastReceiver
。Android IPC 机制允许您验证连接到 IPC 的应用程序的身份,并为每个 IPC 机制设置安全策略。
许多安全元素在 IPC 机制之间是共享的。如果您的 IPC 机制不打算供其他应用程序使用,请将组件清单元素(例如 <service>
元素)中的 android:exported
属性设置为 false
。这对于由同一 UID 内的多个进程组成的应用程序,或者如果您在开发后期决定实际上不想将功能公开为 IPC,但又不想重写代码的情况非常有用。
如果您的 IPC 可供其他应用程序访问,您可以使用 <permission>
元素应用安全策略。如果 IPC 发生在您自己的应用程序之间且使用相同的密钥签名,请在 android:protectionLevel
中使用 signature-level
权限。
Intent
对于活动和广播接收器,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 接收器中执行输入验证。
服务
Service
通常用于为其他应用程序提供功能。每个服务类必须在其清单文件中具有相应的 <service>
声明。
默认情况下,服务不会被导出,也不能被任何其他应用程序调用。但是,如果您向服务声明添加任何 intent 过滤器,它将默认被导出。最好您明确声明 android:exported
属性,以确保其行为符合您的预期。服务还可以使用 android:permission
属性进行保护。通过这样做,其他应用程序需要在自己的清单中声明相应的 <uses-permission>
元素才能启动、停止或绑定到服务。
注意:如果您的应用面向 Android 5.0 (API 级别 21) 或更高版本,请使用 **JobScheduler**
执行后台服务。
服务可以使用权限保护对其进行的单个 IPC 调用。这通过在执行调用的实现之前调用 checkCallingPermission()
来完成。我们建议在清单中使用声明性权限,因为这些权限不太容易被忽视。
注意:不要混淆客户端和服务器权限;确保被调用应用具有适当的权限,并验证您是否授予调用应用相同的权限。
Binder 和 Messenger 接口
在 Android 上,使用 Binder
或 Messenger
是 RPC 样式 IPC 的首选机制。它们提供定义良好的接口,如果需要,可以实现端点的相互认证。
我们建议您以不需要特定于接口的权限检查的方式设计您的应用接口。Binder
和 Messenger
对象未在应用程序清单中声明,因此您不能直接对它们应用声明式权限。它们通常继承在应用程序清单中为其实现的 Service
或 Activity
声明的权限。如果您正在创建需要身份验证和/或访问控制的接口,则必须在 Binder
或 Messenger
接口中明确将这些控制作为代码添加。
如果您提供了一个需要访问控制的接口,请使用 checkCallingPermission()
来验证调用者是否具有所需的权限。这在代表调用者访问服务之前尤为重要,因为您的应用程序的身份会传递给其他接口。如果您正在调用 Service
提供的接口,如果您的应用程序没有访问给定服务的权限,则 bindService()
调用可能会失败。如果您需要允许外部进程与您的应用程序交互但它没有必要的权限,您可以使用 clearCallingIdentity()
方法。此方法执行对您的应用程序接口的调用,就像您的应用程序自己进行调用一样,而不是外部调用者。您稍后可以使用 restoreCallingIdentity()
方法恢复调用者权限。
有关使用服务执行 IPC 的更多信息,请参阅绑定服务。
广播接收器
BroadcastReceiver
处理由 Intent
发起的异步请求。
默认情况下,接收器是导出的,可以被任何其他应用程序调用。如果您的 BroadcastReceiver
旨在供其他应用程序使用,您可能希望使用应用程序清单中的 <receiver>
元素对接收器应用安全权限。这可以防止没有适当权限的应用程序向 BroadcastReceiver
发送 Intent。
动态加载代码的安全性
我们强烈不鼓励从您的应用 APK 外部加载代码。这样做会显著增加因代码注入或代码篡改而导致应用受损的可能性。它还会增加版本管理和应用测试的复杂性——并且可能使验证应用行为变得不可能,因此在某些环境中可能被禁止。
如果您的应用程序确实动态加载代码,最重要的一点是,动态加载的代码与应用程序 APK 具有相同的安全权限。用户根据您的身份决定安装您的应用程序,并且用户期望您提供应用程序中运行的任何代码,包括动态加载的代码。
许多应用程序试图从不安全的位置加载代码,例如通过未加密的协议从网络下载或从可全局写入的位置(例如外部存储)加载代码。这些位置可能允许网络上的某个人修改传输中的内容,或者设备上的另一个应用程序修改设备上的内容。另一方面,直接包含在您的 APK 中的模块不能被其他应用程序修改。无论代码是原生库还是使用 DexClassLoader
加载的类,情况都是如此。
虚拟机安全性
Dalvik 是 Android 的运行时虚拟机 (VM)。Dalvik 是专门为 Android 构建的,但与其他虚拟机中安全代码相关的许多问题也适用于 Android。一般来说,您不需要担心与虚拟机相关的安全问题。您的应用程序在一个安全的沙盒环境中运行,因此系统上的其他进程无法访问您的代码或私有数据。
如果您有兴趣了解更多关于虚拟机安全性的信息,请熟悉一些现有的相关文献。其中两个更受欢迎的资源是:
本文档重点介绍 Android 特有的或与其他 VM 环境不同的领域。对于在其他环境中具有 VM 编程经验的开发人员,编写 Android 应用程序可能有两个主要区别:
- 某些虚拟机,例如 JVM 或 .NET 运行时,充当安全边界,将代码与底层操作系统功能隔离。在 Android 上,Dalvik VM 不是安全边界——应用程序沙盒是在操作系统级别实现的,因此 Dalvik 可以在同一应用程序中与原生代码互操作,而无需任何安全限制。
- 鉴于移动设备存储空间有限,开发者通常希望构建模块化应用程序并使用动态类加载。执行此操作时,请考虑您获取应用程序逻辑的来源以及您在本地存储的位置。不要从未经验证的来源(例如不安全的网络来源或外部存储)使用动态类加载,因为这些代码可能会被修改以包含恶意行为。
原生代码安全性
一般来说,我们建议使用 Android SDK 进行应用程序开发,而不是使用 Android NDK 进行原生代码开发。使用原生代码构建的应用程序更复杂,可移植性更差,并且更有可能包含常见的内存损坏错误,例如缓冲区溢出。
Android 是基于 Linux 内核构建的,如果您正在使用原生代码,那么熟悉 Linux 开发安全最佳实践尤其有用。Linux 安全实践超出了本文档的范围,但最受欢迎的资源之一是安全编程 HOWTO - 创建安全软件。
Android 与大多数 Linux 环境之间的一个重要区别是应用程序沙盒。在 Android 上,所有应用程序(包括使用原生代码编写的应用程序)都在应用程序沙盒中运行。对于熟悉 Linux 的开发人员来说,一个很好的理解方式是知道每个应用程序都分配了一个具有非常有限权限的唯一用户标识符 (UID)。这在Android 安全概述中进行了更详细的讨论,即使您正在使用原生代码,您也应该熟悉应用程序权限。