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 Re-Delegation: Attacks and Defenses》研究论文https://www.usenix.org/legacy/event/sec11/tech/full_papers/Felt.pdf。
权限定义
定义满足您的安全要求的最小权限集。对于大多数应用程序来说,创建新的权限相对来说并不常见,因为系统定义的权限涵盖了许多情况。在适当的情况下,请使用现有权限执行访问检查。
如果您需要新的权限,请考虑是否可以使用签名保护级别完成您的任务。签名权限对用户是透明的,并且仅允许与执行权限检查的应用程序相同的开发人员签名的应用程序访问。
如果仍然需要创建新的权限,请使用<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 云消息传递 (FCM) 和 IP 网络将数据消息从 Web 服务器发送到用户设备上的应用程序。
请注意,SMS 在网络或设备上既未加密也未进行强身份验证。特别是,任何 SMS 接收者都应该期望恶意用户可能已将 SMS 发送到您的应用程序。不要依赖未经身份验证的 SMS 数据来执行敏感命令。此外,请注意,SMS 在网络上可能会受到欺骗和/或拦截。在 Android 设备本身,SMS 消息以广播 intent 的形式传输,因此其他具有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 内容,所以使用不当会导致常见的 Web 安全问题,例如 跨站脚本攻击(JavaScript 注入)。Android 包含许多机制来通过将 WebView
的功能限制为您的应用程序所需的最低功能来减少这些潜在问题的范围。
如果您的应用程序不在 WebView
中直接使用 JavaScript,请不要调用 setJavaScriptEnabled
。一些示例代码使用此方法;如果您将使用此方法的示例代码重新用于生产应用程序,则如果不需要,请删除该方法调用。默认情况下,WebView
不会执行 JavaScript,因此跨站脚本攻击是不可能的。
请谨慎使用 addJavaScriptInterface()
,因为它允许 JavaScript 调用通常为 Android 应用程序保留的操作。如果您使用它,请仅对所有输入都可信的网页公开 addJavaScriptInterface()
。如果允许不可信输入,不可信 JavaScript 可能会在您的应用程序中调用 Android 方法。一般来说,我们建议仅对包含在您的应用程序 APK 中的 JavaScript 公开 addJavaScriptInterface()
。
如果您的应用程序使用 WebView
访问敏感数据,请考虑使用 clearCache()
方法来删除本地存储的任何文件。您还可以使用服务器端标头(例如 no-store
)来指示应用程序不应缓存特定内容。
运行 Android 4.4(API 级别 19)之前平台的设备使用 webkit
的版本,该版本存在许多安全问题。作为一种解决方法,如果您的应用程序在这些设备上运行,它必须确认 WebView
对象仅显示可信内容。为了确保您的应用程序不受 SSL 中潜在漏洞的影响,请使用可更新的安全 Provider
对象,如 更新您的安全提供程序以防范 SSL 利用 中所述。如果您的应用程序必须呈现来自开放网络的内容,请考虑提供您自己的渲染器,以便您可以使用最新的安全补丁对其进行更新。
凭据请求
凭据请求是攻击的载体。以下是一些提示,可以帮助您使 Android 应用程序中的凭据请求更加安全。
最小化凭据暴露
- 避免不必要的凭据请求。为了使钓鱼攻击更加显眼,并且不太可能成功,请尽量减少请求用户凭据的频率。相反,请使用授权令牌并刷新它。仅请求身份验证和授权所需的最小凭据信息。
- 安全存储凭据。使用 凭据管理器 使用密钥启用无密码身份验证,或使用 Sign in with Google 等方案实现联合登录。如果您必须使用传统的密码身份验证,请不要在设备上存储用户 ID 和密码。相反,请使用用户提供的用户名和密码执行初始身份验证,然后使用短暂的、特定于服务的授权令牌。
- 限制权限范围。不要为仅需要更窄范围的任务请求广泛的权限。
- 限制访问令牌。使用短暂令牌执行操作和 API 调用。
- 限制身份验证速率。快速连续的身份验证或授权请求可能是暴力攻击的征兆。将这些速率限制在合理的频率,同时仍然允许应用程序以功能性和用户友好的方式运行。
使用安全身份验证
- 实施密钥。启用密钥作为密码更安全、更用户友好的升级。
- 添加生物识别。提供使用 生物识别身份验证(如指纹或面部识别)以增强安全性。
- 使用联合身份提供者。凭据管理器支持联合身份提供者,例如 Sign in with 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 机制并非旨在供其他应用程序使用,请在组件的清单元素中将 android:exported
属性设置为 false
,例如对于 <service>
元素。这对于由同一 UID 内的多个进程组成的应用程序很有用,或者如果您在开发后期决定实际上不想将功能公开为 IPC,但您不想重写代码。
如果您的 IPC 可供其他应用程序访问,您可以使用 <permission>
元素应用安全策略。如果 IPC 在您自己的应用程序之间,并且使用相同的密钥签名,请在 android:protectionLevel
中使用 signature-level
权限。
意图
对于活动和广播接收器,意图是 Android 上异步 IPC 的首选机制。根据您的应用程序需求,您可以使用 sendBroadcast
、sendOrderedBroadcast
或指向特定应用程序组件的显式意图。出于安全目的,显式意图是首选。
注意:如果您使用意图绑定到 **Service**
,请使用 显式 意图来确保您的应用程序安全。使用隐式意图启动服务是一种安全隐患,因为您无法确定将响应意图的服务,并且用户也无法看到启动的服务。从 Android 5.0(API 级别 21)开始,如果您使用隐式意图调用 **bindService()**
,系统将抛出异常。
请注意,有序广播可以被接收者使用,因此它们可能不会传递给所有应用程序。如果您发送必须传递到特定接收者的意图,您必须使用显式意图,该意图按名称声明接收者。
意图发送者可以通过在方法调用中指定非空权限来验证接收者是否拥有权限。只有具有该权限的应用程序才能接收意图。如果广播意图中的数据可能敏感,请考虑应用权限以确保恶意应用程序无法在没有适当权限的情况下注册以接收这些消息。在这些情况下,您还可以考虑直接调用接收者,而不是引发广播。
注意:意图过滤器不是安全功能。组件可以使用显式意图调用,并且可能没有符合意图过滤器的數據。要确认它是否已正确格式化为调用的接收者、服务或活动,请在您的意图接收器中执行输入验证。
服务
Service
通常用于为其他应用程序提供功能。每个服务类必须在它的清单文件中有一个对应的 <service>
声明。
默认情况下,服务不会导出,并且无法被任何其他应用程序调用。但是,如果您在服务声明中添加了任何意图过滤器,它将默认导出。最好显式声明 android:exported
属性,以确保它按您的意愿运行。服务也可以使用 android:permission
属性进行保护。这样,其他应用程序需要在其自己的清单中声明一个对应的 <uses-permission>
元素,才能启动、停止或绑定到该服务。
注意:如果您的应用程序面向 Android 5.0(API 级别 21)或更高版本,请使用 **JobScheduler**
来执行后台服务。
服务可以使用权限保护对它的单个 IPC 调用。这可以通过在执行调用实现之前调用 checkCallingPermission()
来完成。我们建议使用清单中的声明式权限,因为这些权限不太容易被忽视。
注意:不要混淆客户端和服务器权限;确保被调用的应用程序具有适当的权限,并验证您是否向调用应用程序授予了相同的权限。
Binder 和 Messenger 接口
使用 Binder
或 Messenger
是 Android 上 RPC 样式 IPC 的首选机制。它们提供定义良好的接口,如果需要,这些接口可以实现端点的相互身份验证。
我们建议您设计应用界面时,不要使用需要特定界面权限检查的方式。 Binder
和 Messenger
对象不在应用程序清单中声明,因此您无法直接对它们应用声明式权限。它们通常继承在应用程序清单中为 Service
或 Activity
声明的权限,它们是在其中实现的。如果您正在创建一个需要身份验证和/或访问控制的界面,则必须在 Binder
或 Messenger
界面中显式添加这些控件作为代码。
如果您提供需要访问控制的界面,请使用 checkCallingPermission()
来验证调用者是否拥有所需的权限。这在代表调用者访问服务之前尤为重要,因为您应用程序的身份会传递到其他界面。如果您正在调用 Service
提供的界面,则 bindService()
调用可能会失败,因为您没有访问该服务的权限。如果您需要允许外部进程与您的应用程序交互,但它没有执行此操作的必要权限,则可以使用 clearCallingIdentity()
方法。此方法将调用您的应用程序界面的方法,就好像您的应用程序本身正在执行调用一样,而不是外部调用者。您可以使用 restoreCallingIdentity()
方法稍后恢复调用者的权限。
有关使用服务执行 IPC 的更多信息,请参阅 绑定服务。
广播接收器
一个 BroadcastReceiver
处理由 Intent
发起的异步请求。
默认情况下,接收器是公开的,可以被任何其他应用程序调用。如果您的 BroadcastReceiver
用于其他应用程序,您可能希望使用应用程序清单中的 <receiver>
元素将安全权限应用于接收器。这将阻止没有适当权限的应用程序向 BroadcastReceiver
发送意图。
动态加载代码的安全
我们强烈建议不要从应用程序 APK 之外加载代码。这样做会大大增加应用程序因代码注入或代码篡改而受到攻击的可能性。它还增加了版本管理和应用程序测试的复杂性,并且可能无法验证应用程序的行为,因此在某些环境中可能会被禁止。
如果您的应用程序确实动态加载代码,最重要的是要记住,动态加载的代码以与应用程序 APK 相同的安全权限运行。用户根据您的身份做出安装应用程序的决定,用户期望您提供应用程序中运行的任何代码,包括动态加载的代码。
许多应用程序试图从不安全的位置加载代码,例如从网络上通过未加密的协议下载或从外部存储器等可由全世界写入的位置下载。这些位置可能允许网络上的某个人修改传输中的内容,或者允许用户设备上的另一个应用程序修改设备上的内容。另一方面,直接包含在您的 APK 中的模块无法被其他应用程序修改。无论代码是本机库还是使用 DexClassLoader
加载的类,都是如此。
虚拟机中的安全
Dalvik 是 Android 的运行时虚拟机 (VM)。Dalvik 是专门为 Android 构建的,但其他虚拟机中与安全代码相关的许多问题也适用于 Android。通常,您无需担心与虚拟机相关的安全问题。您的应用程序在安全的沙箱环境中运行,因此系统上的其他进程无法访问您的代码或私有数据。
如果您有兴趣了解更多关于虚拟机安全的信息,请熟悉一些关于此主题的现有文献。两个最受欢迎的资源是
本文档重点关注 Android 特定的或与其他 VM 环境不同的领域。对于在其他环境中具有 VM 编程经验的开发人员来说,在 Android 上编写应用程序时可能会遇到两个主要问题
- 一些虚拟机,例如 JVM 或 .NET 运行时,充当安全边界,将代码与底层操作系统功能隔离开来。在 Android 上,Dalvik VM 不是安全边界,应用程序沙箱是在 OS 级实现的,因此 Dalvik 可以与同一应用程序中的本机代码进行交互,而没有任何安全限制。
- 鉴于移动设备上的存储空间有限,开发人员通常希望构建模块化应用程序并使用动态类加载。在执行此操作时,请考虑从何处检索您的应用程序逻辑以及在本地存储的位置。不要从未经验证的来源(例如不安全的网络来源或外部存储)使用动态类加载,因为该代码可能会被修改为包含恶意行为。
本机代码中的安全
通常,我们建议使用 Android SDK 进行应用程序开发,而不是使用 Android NDK 使用本机代码。使用本机代码构建的应用程序更复杂、可移植性更差,并且更容易出现常见的内存损坏错误,例如缓冲区溢出。
Android 是使用 Linux 内核构建的,如果您使用本机代码,熟悉 Linux 开发安全最佳实践将特别有用。Linux 安全实践超出了本文档的范围,但最受欢迎的资源之一是 安全编程 HOWTO - 创建安全软件。
Android 和大多数 Linux 环境之间的重要区别在于应用程序沙箱。在 Android 上,所有应用程序都在应用程序沙箱中运行,包括使用本机代码编写的应用程序。对于熟悉 Linux 的开发人员来说,一个好的理解方法是知道每个应用程序都获得了具有非常有限权限的唯一用户标识 (UID)。这将在 Android 安全概述 中详细讨论,即使您使用本机代码,您也应该熟悉应用程序权限。