安全机制(Permission与SELinux):Android权限模型、SELinux策略与上下文、应用沙箱与UID隔离、Keystore与加密
聊到Android安全,很多人第一反应就是「权限弹窗」。其实这只是冰山一角。我做了这么多年Framework,见过太多应用因为权限问题崩溃,也见过厂商因为SELinux策略没写好导致系统起不来。今天咱们就把这套安全体系拆开看看。
一、Android权限模型:普通、危险、签名权限
Android的权限模型,说白了就是一套「谁可以干什么」的规则。从API 23开始,Google把权限分成了几类,咱们一个个说。
1. 普通权限(Normal Permissions)
这类权限不涉及用户隐私,系统直接授予。比如INTERNET、ACCESS_NETWORK_STATE。你在AndroidManifest.xml里声明了,安装时就自动授权,用户甚至感知不到。
2. 危险权限(Dangerous Permissions)
这类权限涉及用户隐私数据,比如读取联系人、访问位置、使用相机。系统会在运行时弹窗让用户确认。嗯,这里要注意:危险权限是分组管理的。比如你申请了READ_CONTACTS,如果用户同意了,同组的WRITE_CONTACTS也会自动获得授权。
权限分组示例:
- CALENDAR:READ_CALENDAR, WRITE_CALENDAR
- CAMERA:CAMERA
- CONTACTS:READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS
- LOCATION:ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
- PHONE:READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG, ADD_VOICEMAIL, USE_SIP
3. 签名权限(Signature Permissions)
这个有意思。只有使用相同签名的应用才能获得该权限。我在项目中遇到过这样的场景:系统应用和厂商预装应用之间需要共享数据,但又不想暴露给第三方。这时候签名权限就派上用场了。说白了,就是「自己人才能用」。
我的经验:定义签名权限时,记得在AndroidManifest.xml中设置protectionLevel="signature"。我曾经见过有人误写成signatureOrSystem,结果系统应用也能访问,导致数据泄露风险。
<!-- 定义签名权限 -->
<permission
android:name="com.example.MY_SIGNATURE_PERMISSION"
android:protectionLevel="signature" />
<!-- 声明使用 -->
<uses-permission android:name="com.example.MY_SIGNATURE_PERMISSION" />
二、SELinux策略与上下文:强制访问控制的守护者
你想想看,传统的Linux权限模型(DAC)只检查UID/GID。如果应用拿到了root权限,理论上可以为所欲为。SELinux引入了强制访问控制(MAC),每个进程、每个文件都有安全上下文,规则由策略文件定义。
我记得有一次调试系统服务,发现某个进程总是无法访问一个设备节点。查了半天,原来是SELinux策略没写对。嗯,从那以后我养成了习惯:遇到权限问题,先看dmesg | grep avc。
SELinux上下文格式
每个安全上下文由四部分组成:user:role:type:level。对于Android来说,最核心的是type。
# 查看进程上下文
ps -Z
# 查看文件上下文
ls -Z /data/data/com.example.app
常见类型示例:
| 类型 | 描述 |
|---|---|
| u:r:init:s0 | init进程 |
| u:r:system_server:s0 | 系统服务进程 |
| u:r:untrusted_app:s0 | 第三方应用 |
| u:object_r:app_data_file:s0 | 应用私有数据文件 |
SELinux策略编写
策略文件通常放在device/<vendor>/<device>/sepolicy/目录下。写策略其实就是在定义「谁可以访问谁」。
# 允许system_app读取某个设备节点
allow system_app my_device:chr_file read;
# 允许untrusted_app创建socket
allow untrusted_app self:socket create_socket_perms;
避坑指南:我曾经在项目中遇到一个问题:添加了新的系统服务后,SELinux一直报avc denied。排查后发现是忘了给新服务定义type和domain。记住:每个新服务都需要在sepolicy中声明type和domain,并编写对应的.te文件。
三、应用沙箱与UID隔离
Android的沙箱机制,说白了就是「每个应用一个独立空间」。安装应用时,系统会分配一个唯一的UID。这个UID决定了应用能访问哪些资源。
你想想看,应用A的UID是10001,应用B的UID是10002。它们各自的数据目录是/data/data/com.example.a和/data/data/com.example.b。没有特殊权限,谁也看不到谁的数据。
UID分配规则:
- 系统应用:UID从1000开始(如system为1000,radio为1001)
- 普通应用:UID从10000开始
- 共享UID:如果多个应用使用相同签名并声明
sharedUserId,它们共享同一个UID
我在项目中遇到过共享UID的坑。两个应用声明了相同的sharedUserId,但签名不一致,结果安装时报错INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。嗯,这个错误很常见,排查起来也简单:检查签名是否一致即可。
四、Keystore与加密
Android Keystore系统,说白了就是一个「硬件级保险箱」。密钥一旦生成,就存储在TEE(可信执行环境)或SE(安全元件)中,应用只能通过API使用密钥,无法直接读取密钥内容。
Keystore的核心能力
- 密钥生成:在安全硬件中生成密钥对
- 密钥使用:签名、验证、加密、解密
- 密钥保护:密钥永远不会离开安全硬件
- 用户认证绑定:可以要求使用密钥前必须验证指纹或PIN码
// 生成RSA密钥对并存入Keystore
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true) // 需要用户认证
.build();
keyPairGenerator.initialize(spec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
我的建议:敏感数据(如Token、密码)不要直接存SharedPreferences。用EncryptedSharedPreferences,它底层就是Keystore + AES加密。我在项目中用过,既安全又方便。
加密实践
Android支持多种加密算法,但要注意:不要自己实现加密逻辑。用现成的Android Security Library或Jetpack Security。
// 使用EncryptedSharedPreferences
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// 写入加密数据
sharedPreferences.edit().putString("token", "my_secret_token").apply();
五、知识体系总览
下面这张图是我梳理的Android安全机制核心脉络。你可以看到,从应用层到内核层,每一层都有对应的安全措施。
这张图从下往上看,底层是硬件加密和Keystore,往上是SELinux强制访问控制,再往上是Framework层的权限管理,最顶层是应用沙箱和UID隔离。每一层都环环相扣,缺一不可。
最后提醒一句:安全不是某个模块的事,而是一个系统工程。我见过太多开发者只关注权限弹窗,却忽略了SELinux策略和Keystore的使用。记住:攻击者往往从最薄弱的环节入手。
公众号:蓝海资料掘金营,微信deep3321