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 安全审计清单

做路由策略安全审计,我一般会检查这几个点:

  1. 权限最小化:只有需要修改路由的组件才持有 MODIFY_AUDIO_SETTINGS 权限。
  2. 路由变更日志:记录每次路由切换的调用方、时间、目标设备。
  3. 策略覆盖检查:确保系统策略(如通话路由)不会被第三方App覆盖。
  4. 设备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 知识体系总览

说了这么多,咱们用一张图来总结音频安全的三大块:

音频安全三大防线 权限控制 • RECORD_AUDIO 录音权限 • MODIFY_AUDIO_SETTINGS 路由权限 • CAPTURE_AUDIO_OUTPUT 捕获权限 • 运行时权限检查 • 权限生命周期管理 → 谁可以碰音频? 路由策略审计 • 路由变更来源追踪 • 调用栈分析 • 信任设备白名单 • 策略覆盖检查 • 非法设备ID过滤 → 路由被谁改了? 防止信息泄露 • 录音泄露防护 • 播放泄露防护 • 路由泄露防护 • 敏感音频强制路由 • 设备切换监听 → 音频去了不该去的地方? 安全不是单一功能,而是贯穿整个音频栈的设计理念

29.5 写在最后

音频安全这件事,说白了就是「管好入口、看好出口、盯住路由」。权限控制是基础,路由审计是保障,防泄露是目标。三者缺一不可。

我见过太多团队只做了权限控制,就觉得「安全了」。结果路由策略被绕过,音频数据从蓝牙泄露出去。或者录音权限检查了,但播放时没注意设备切换,隐私内容从扬声器放了出来。

嗯,安全没有银弹。但只要你把这三道防线都扎扎实实做好,大部分攻击就能挡在门外。

📌 核心要点回顾
  • 权限控制:最小化原则,运行时检查,生命周期管理
  • 路由审计:追踪变更来源,验证调用方,记录日志
  • 防泄露:敏感音频强制路由,设备切换监听,安全代理模式

公众号:蓝海资料掘金营,微信deep3321