29、音频策略与安全:音频权限控制、路由策略安全审计、防止音频信息泄露
说到音频安全,很多人第一反应是「权限控制」。其实这只是冰山一角。
我这些年做音频系统,见过太多「看起来没问题,实际上漏洞百出」的设计。比如某个App偷偷录音、路由策略被绕过导致隐私泄露……这些坑,我踩过不少。
今天咱们就聊聊音频安全的三道防线:权限控制、路由审计、防泄露。
29.1 音频权限控制:谁可以碰音频?
Android的音频权限体系,说白了就是一套「门禁系统」。谁可以录音?谁可以播放?谁可以修改路由?每个动作都有对应的权限。
29.1.1 核心权限清单
| 权限名称 | 保护级别 | 用途 |
|---|---|---|
| RECORD_AUDIO | 危险权限 | 录音、语音识别、通话录音 |
| MODIFY_AUDIO_SETTINGS | 普通权限 | 修改音量、路由、音效 |
| CAPTURE_AUDIO_OUTPUT | 系统权限 | 捕获系统播放的音频(屏幕录制) |
| BIND_ACCESSIBILITY_SERVICE | 系统权限 | 辅助功能服务,可监听音频事件 |
嗯,这里要注意:RECORD_AUDIO 是危险权限,运行时必须弹窗让用户确认。但 MODIFY_AUDIO_SETTINGS 只是普通权限,安装时自动授予。我见过不少开发者以为「改路由不需要权限」,结果在Android 12上直接崩溃——因为系统收紧了权限检查。
我曾经在一个项目中,发现某个第三方SDK偷偷调用了
AudioManager.setSpeakerphoneOn(true),把通话切到免提。查了半天,原来它只声明了 MODIFY_AUDIO_SETTINGS,但系统并没有阻止它。从Android 13开始,Google终于加上了运行时检查——改路由也需要用户授权了。
29.1.2 权限检查的最佳实践
我个人习惯在每次操作音频设备前,都做一次权限检查。别嫌麻烦,安全无小事。
// 检查录音权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_CODE_RECORD);
return;
}
// 检查修改音频设置权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MODIFY_AUDIO_SETTINGS)
!= PackageManager.PERMISSION_GRANTED) {
// 普通权限通常已授予,但最好还是检查一下
Log.w(TAG, "MODIFY_AUDIO_SETTINGS not granted, routing may fail");
}
对于系统级应用(比如预装的车载音频服务),可以使用
android.permission.CAPTURE_AUDIO_OUTPUT 来捕获系统音频输出。但普通App别想了——这个权限只给系统签名应用。
29.2 路由策略安全审计:谁在控制音频流向?
路由策略,说白了就是「音频该走哪条路」。但安全审计要问的是:这条路是谁选的?有没有被篡改?
29.2.1 路由策略的潜在风险
你想想看,如果某个恶意App能随意修改音频路由,会发生什么?
- 窃听风险:把通话音频路由到扬声器,同时开启录音——这就是经典的「免提窃听」攻击。
- 干扰风险:把导航提示路由到蓝牙耳机,但音乐走扬声器——用户体验极差,但更危险的是可能掩盖重要提示。
- 绕过策略:某些App通过
AudioTrack直接指定设备ID,绕过系统的路由策略。
我在项目中遇到过最离谱的一次:某个车载App在后台偷偷把媒体音频路由到蓝牙,导致用户打电话时听不到对方声音。查了三天,发现是它调用了 AudioManager.setPreferredDevice(),而且没有在 onPause() 中恢复。
29.2.2 安全审计清单
做路由策略安全审计,我一般会检查这几个点:
- 权限最小化:只有需要修改路由的组件才持有
MODIFY_AUDIO_SETTINGS权限。 - 路由变更日志:记录每次路由切换的调用方、时间、目标设备。
- 策略覆盖检查:确保系统策略(如通话路由)不会被第三方App覆盖。
- 设备ID验证:防止通过
setPreferredDevice指定非法设备。
在
AudioPolicy 中,可以重写 onRoutingChanged() 来监控路由变化:
class SecureAudioPolicy extends AudioPolicy {
@Override
public void onRoutingChanged(AudioPolicy policy) {
AudioRouting routing = policy.getRouting();
// 记录调用栈,找出是谁改了路由
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
Log.i(TAG, "Routing changed by: " + stack[3].getClassName() + "." + stack[3].getMethodName());
// 检查是否来自可信来源
if (!isTrustedCaller(stack[3].getClassName())) {
Log.w(TAG, "Untrusted caller modified routing!");
// 恢复默认路由
policy.setPreferredDeviceForStrategy(
AudioAttributes.USAGE_MEDIA,
null // 清除偏好,恢复系统默认
);
}
}
}
29.3 防止音频信息泄露:从源头到终点
音频信息泄露,说白了就是「不该听到的人听到了」。这可能是录音泄露、播放泄露,也可能是路由泄露。
29.3.1 录音泄露防护
录音是最直接的泄露途径。Android从10开始引入了 MediaRecorder 的隐私保护,但还不够。
- 录音指示器:Android 12+ 在状态栏显示绿色图标,表示有App正在录音。但用户不一定注意得到。
- 录音权限生命周期:建议在
onPause()中释放录音资源,避免后台持续录音。 - 麦克风独占:使用
AudioRecord时,设置setPreferredDevice为麦克风,防止其他App同时录音。
我曾经在审计一个语音助手App时发现,它在后台持续录音,即使没有唤醒词。原因是它注册了
MediaRecorder 但没有在 onStop() 中释放。用户以为「我没说话就不会被录」,但实际上音频数据一直在内存中缓存。这就是典型的「静默录音」漏洞。
29.3.2 播放泄露防护
播放泄露往往被忽视。比如:
- 扬声器泄露:在公共场合用扬声器播放私密语音消息。
- 蓝牙泄露:蓝牙耳机断开后,音频自动切到扬声器——如果用户没注意,隐私就暴露了。
- 音频焦点泄露:一个App播放时,另一个App可以「偷听」音频焦点事件,推断用户行为。
我的建议是:对于敏感音频(如支付确认音、语音密码),强制使用有线耳机或听筒播放,并监听设备切换事件。
// 强制使用听筒播放敏感音频
AudioAttributes attrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
AudioTrack track = new AudioTrack.Builder()
.setAudioAttributes(attrs)
.setAudioFormat(format)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.build();
// 设置偏好设备为听筒(设备ID 0 通常代表听筒)
track.setPreferredDevice(
AudioDeviceInfo.convertDeviceIdToInternal(0)
);
29.3.3 路由泄露防护
路由泄露是最隐蔽的。比如:
- 蓝牙耳机断开:音频自动切到扬声器,但用户戴着耳机以为还在蓝牙上。
- USB音频设备热插拔:插拔瞬间可能产生短暂的音频路由混乱。
- 多应用路由冲突:两个App同时请求不同的路由策略,系统可能选择「最不安全」的那个。
我建议在应用层做一层「安全路由代理」:
class SecureRoutingManager {
private AudioManager audioManager;
private Set<AudioDeviceInfo> trustedDevices;
public boolean routeToDevice(AudioDeviceInfo device) {
// 1. 检查设备是否在信任列表中
if (!trustedDevices.contains(device)) {
Log.w(TAG, "Untrusted device: " + device.getProductName());
return false;
}
// 2. 检查当前是否有敏感音频在播放
if (isSensitiveAudioPlaying()) {
// 强制使用听筒
audioManager.setSpeakerphoneOn(false);
audioManager.setWiredHeadsetOn(true);
return true;
}
// 3. 正常路由
return audioManager.setPreferredDevice(device);
}
}
29.4 知识体系总览
说了这么多,咱们用一张图来总结音频安全的三大块:
29.5 写在最后
音频安全这件事,说白了就是「管好入口、看好出口、盯住路由」。权限控制是基础,路由审计是保障,防泄露是目标。三者缺一不可。
我见过太多团队只做了权限控制,就觉得「安全了」。结果路由策略被绕过,音频数据从蓝牙泄露出去。或者录音权限检查了,但播放时没注意设备切换,隐私内容从扬声器放了出来。
嗯,安全没有银弹。但只要你把这三道防线都扎扎实实做好,大部分攻击就能挡在门外。
- 权限控制:最小化原则,运行时检查,生命周期管理
- 路由审计:追踪变更来源,验证调用方,记录日志
- 防泄露:敏感音频强制路由,设备切换监听,安全代理模式