DRM(数字版权管理)是 Android 上商业价值极高的子系统。每当用户在 Netflix 点播电影、在 Google Play 租赁影片,或是通过付费应用观看体育直播时,DRM 框架就会在后台静默完成许可证协商、内容解密以及输出保护策略强制校验,整个过程对用户完全无感知。本章将剖析 Android DRM 架构,从 Java API 表层,向下经过原生框架、跨越 HAL 边界,直至执行实际密码运算的厂商提供插件实现。本章先从高层架构概述开始(42.1 节),接着讲解应用调用的框架代码(42.2 节);随后介绍厂商插件必须实现的稳定 AIDL HAL 契约(42.3 节);讲解几乎所有 Android 设备都搭载的 Widevine DRM 系统(42.4 节);逐行解读 ClearKey 参考插件(42.5 节);之后介绍安全编解码通路,防止解密后的裸帧被捕获(42.6 节);介绍指标与日志基础设施,在不泄露受保护内容的前提下完成问题诊断(42.7 节);再讲解 Android 17 的 DRM HAL 变更:将 AIDL 接口冻结在 V2 版本,新增密钥句柄解密‑解码快速通路(42.8 节);最后是动手实践练习(42.9 节)。42.1 DRM 架构概述42.1.1 DRM 所要解决的问题内容版权方(电影公司、唱片公司、体育赛事联盟)向流媒体平台授权内容时会附加严苛约束:内容在传输与持久存储阶段必须加密;解密密钥绝不可以暴露给应用层代码;解密后的画面帧需要防止截屏、HDMI 抓取;播放结束后系统要向许可证服务器上报安全停止记录。Android DRM 框架正是为满足上述需求而设计,同时向应用开发者提供一套与具体 DRM 方案无关的简洁 API。42.1.2 核心组件DRM 子系统包含四大核心组件,跨三个进程边界:MediaDrm:应用用来协商许可证、管理会话的 Java API,运行在应用进程。MediaCrypto:配套 Java API,用于把 DRM 会话对接编解码器。同样运行在应用进程,全部密码运算通过 Binder 跨进程委托给底层。DRM 框架(libmediadrm):运行在 mediaserver /mediadrmserver 中的 C++ 原生层,负责调用对应 HAL 后端,通过 DrmSessionManager 管理会话,采集指标数据。DRM HAL 插件:厂商提供的 AIDL 服务(或旧版 HIDL 服务),实现真正的密码运算逻辑,运行独立进程。源码路径:frameworks/av/drm/ 原生框架代码 frameworks/base/media/java/android/media/MediaDrm.java Java API hardware/interfaces/drm/ HAL接口定义 frameworks/av/drm/mediadrm/plugins/clearkey/ 参考插件42.1.3 端到端架构示意图下图展示受 DRM 保护的播放会话,从应用层经过框架到硬件插件的完整流向。42.1.4 基于 UUID 的方案选择每一套 DRM 方案都使用 16 字节 UUID 标识。当应用遇到受 DRM 保护的内容,会从内容元数据读取方案 UUID(一般来自 ISO BMFF 封装的 PSSH 盒,或是 DASH 清单的 ContentProtection 节点),查询设备是否支持该 DRM。// 摘自 frameworks/base/media/java/android/media/MediaDrm.java public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) { return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null, SECURITY_LEVEL_UNKNOWN); }常用 UUID:DRM 方案UUIDWidevineedef8ba9‑79d6‑4ace‑a3c8‑27dcd51d21edClearKey(通用 PSSH)1077efec‑c0b2‑4d02‑ace3‑3c1e52e2fb4bClearKeye2719d58‑a985‑b3c9‑781a‑b030af78d30ePlayReady9a04f079‑9840‑4286‑ab92‑e65be0885f95原生层中,DrmHal::isCryptoSchemeSupported()会优先尝试 AIDL HAL,失败则回退到传统 HIDL HAL。// 摘自 frameworks/av/drm/libmediadrm/DrmHal.cpp DrmStatus DrmHal::isCryptoSchemeSupported(const uint8_t uuid[16], const String8 mimeType, DrmPlugin::SecurityLevel securityLevel, bool* result) { DrmStatus statusResult = mDrmHalAidl-isCryptoSchemeSupported(uuid, mimeType, securityLevel, result); if (*result) return statusResult; return mDrmHalHidl-isCryptoSchemeSupported(uuid, mimeType, securityLevel, result); }这套双后端模式贯穿 DrmHal 全部方法:优先走 AIDL;若 AIDL HAL 初始化失败(initCheck () != OK),就降级使用 HIDL。42.1.5 播放生命周期一次完整 DRM 播放会话流程步骤:42.1.6 安全等级DRM HAL 定义一套安全等级体系,用来描述设备 DRM 实现的安全强度,在 AIDL 枚举中定义。// 摘自 hardware/interfaces/drm/aidl/android/hardware/drm/SecurityLevel.aidl enum SecurityLevel { UNKNOWN, // 无法判定安全等级 SW_SECURE_CRYPTO, // 软件白盒密码实现 SW_SECURE_DECODE, // 软件白盒密码 + 混淆解码器 HW_SECURE_CRYPTO, // 密钥管理与密码运算运行在TEE HW_SECURE_DECODE, // 密钥管理、密码运算、解码全部运行在TEE HW_SECURE_ALL, // 全部处理逻辑运行在TEE(压缩与非压缩数据) DEFAULT, // 设备支持的最高等级 }更高安全等级可以解锁更高画质内容。具备 HW_SECURE_ALL 的设备可以播放 4K HDR 付费内容;仅支持 SW_SECURE_CRYPTO 的设备可能只能播放标清。安全等级在打开会话时指定。// 摘自 frameworks/base/media/java/android/media/MediaDrm.java public byte[] openSession(@SecurityLevel int level) throws NotProvisionedException, ResourceBusyException { byte[] sessionId = openSessionNative(level); mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId)); return sessionId; }42.2 DRM 框架42.2.1 源码目录结构DRM 框架代码位于frameworks/av/drm/,拆分为多个库与子目录:frameworks/av/drm/ drmserver/ 传统DRM管理服务(OMA DRM) DrmManager.cpp DrmManagerService.cpp main_drmserver.cpp libdrmframework/ 传统DRM API客户端库 libmediadrm/ DRM核心框架库 DrmHal.cpp AIDL+HIDL统一入口 DrmHalAidl.cpp AIDL HAL封装层(44 KB) DrmHalHidl.cpp HIDL HAL封装层(56 KB) CryptoHal.cpp Crypto路由层 CryptoHalAidl.cpp AIDL Crypto封装 CryptoHalHidl.cpp HIDL Crypto封装 DrmHalListener.cpp HAL向框架的事件分发 DrmSessionManager.cpp 会话生命周期与资源管理 DrmMetrics.cpp 指标采集(protobuf) DrmMetricsLogger.cpp 向MediaMetrics上报指标 DrmMetricsConsumer.cpp 指标导出为PersistableBundle DrmUtils.cpp HAL发现与工厂创建 DrmPluginPath.cpp 插件动态库路径解析 SharedLibrary.cpp dlopen/dlsym封装 DrmStatus.cpp 状态码转换 PluginMetricsReporting.cpp include/mediadrm/ 对外头文件 libmediadrmrkp/ 远程密钥预配支持 mediadrm/ plugins/ clearkey/ ClearKey参考插件 mediacas/ 条件访问系统(CAS) common/ 公共工具42.2.2 MediaDrm Java APIMediaDrm 类是面向应用的主 API,位于frameworks/base/media/java/android/media/MediaDrm.java。它是 final 类,实现 AutoCloseable,开发者使用 try‑with‑resources 语法时会话资源会自动回收。关键设计特点:基于 UUID 构造:为指定 DRM 方案 UUID 创建 MediaDrm 实例。构造函数调用native_setup(),连接原生 DrmMetricsLogger,继而创建 DrmHal 对象。面向会话:所有密钥操作(getKeyRequest、provideKeyResponse 等)都基于不透明字节数组 sessionId 标识的会话执行。监听器架构:支持四类监听器,底层通过 ConcurrentHashMapInteger, ListenerWithExecutor 统一管理。// 摘自 frameworks/base/media/java/android/media/MediaDrm.java private static final int DRM_EVENT = 200; private static final int EXPIRATION_UPDATE = 201; private static final int KEY_STATUS_CHANGE = 202; private static final int SESSION_LOST_STATE = 203; private final MapInteger, ListenerWithExecutor mListenerMap = new ConcurrentHashMap();事件从 HAL 插件经由 IDrmPluginListener AIDL 接口发出,经过原生 DrmHalListener,到达MediaDrm.postEventFromNative(),再在对应执行器线程分发到注册的监听器。42.2.3 密钥请求 / 响应流程许可证获取是 DRM 的核心流程。应用调用getKeyRequest()生成不透明许可证请求,通过 HTTPS 发给许可证服务器,再将服务器返回结果回传给框架。对应底层调用:DrmHal::getKeyRequest→IDrmPlugin::getKeyRequest;DrmHal::provideKeyResponse→IDrmPlugin::provideKeyResponse。密钥类型决定行为:密钥类型常量行为流式KEY_TYPE_STREAMING (1)密钥仅对当前会话有效离线KEY_TYPE_OFFLINE (2)密钥持久化,无网络可用释放KEY_TYPE_RELEASE (3)释放之前保存的离线密钥插件返回的请求类型告诉应用下一步动作:请求类型常量含义初始REQUEST_TYPE_INITIAL (0)首次许可证请求续期REQUEST_TYPE_RENEWAL (1)许可证到期前更新释放REQUEST_TYPE_RELEASE (2)密钥释放确认无REQUEST_TYPE_NONE (3)密钥已就绪,无需发起请求更新REQUEST_TYPE_UPDATE (4)密钥已加载,但需要更新参数42.2.4 MediaCrypto — 编解码器桥梁MediaCrypto 是 DRM 会话和媒体编解码器之间的桥梁,代码位于frameworks/base/media/java/android/media/MediaCrypto.java,接口比 MediaDrm 简单。// 摘自 frameworks/base/media/java/android/media/MediaCrypto.java public final class MediaCrypto { public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid); public MediaCrypto(@NonNull UUID uuid, @NonNull byte[] sessionId) throws MediaCryptoException; public final native boolean requiresSecureDecoderComponent( @NonNull String mime); public final native void setMediaDrmSession(@NonNull byte[] sessionId) throws MediaCryptoException; public native final void release(); }requiresSecureDecoderComponent()至关重要:查询 HAL 插件,确认当前安全策略是否要求使用安全解码器。若返回 true,应用配置 MediaCodec 时必须带上CONFIGURE_FLAG_SECURE标志,所有解码帧存放在安全受保护内存,CPU 无法直接读取。42.2.5 DrmHal — 原生统一入口DrmHal 类位于frameworks/av/drm/libmediadrm/DrmHal.cpp,是一层薄路由层,同时持有 AIDL 后端 DrmHalAidl 与 HIDL 后端 DrmHalHidl。// 摘自 frameworks/av/drm/libmediadrm/DrmHal.cpp DrmHal::DrmHal() { mDrmHalHidl = spDrmHalHidl::make(); mDrmHalAidl = spDrmHalAidl::make(); }全部 API 方法遵循同一模式:优先尝试 AIDL,失败回退 HIDL。该设计保证兼容搭载 HIDL DRM HAL 的旧设备,同时现代设备优先使用新版 AIDL 接口。createPlugin()展示降级逻辑:// 摘自 frameworks/av/drm/libmediadrm/DrmHal.cpp DrmStatus DrmHal::createPlugin(const uint8_t uuid[16], const String8 appPackageName) { return mDrmHalAidl-createPlugin(uuid, appPackageName) == OK ? DrmStatus(OK) : mDrmHalHidl-createPlugin(uuid, appPackageName); }42.2.6 DrmHalAidl — AIDL 后端DrmHalAidl 位于frameworks/av/drm/libmediadrm/DrmHalAidl.cpp,约 1260 行,包含完整 AIDL 集成逻辑。初始化阶段通过 AServiceManager 发现 AIDL DRM HAL 服务,查询其支持的加密方案,通过工厂实例化对应 IDrmPlugin。该类完成大量类型转换:将框架旧类型(Vectoruint8_t、KeyedVectorString8,String8)转换为 AIDL 类型(std::vectoruint8_t、std::vectorKeyValue)。toKeyValueVector()与toKeyedVector()辅助函数完成转换。// 摘自 frameworks/av/drm/libmediadrm/DrmHalAidl.cpp static std::vectorKeyValue toKeyValueVector( const KeyedVectorString8, String8 keyedVector) { std::vectorKeyValue stdKeyedVector; for (size_t i = 0; i keyedVector.size(); i++) { KeyValue keyValue; keyValue.key = toStdString(keyedVector.keyAt(i)); keyValue.value = toStdString(keyedVector.valueAt(i)); stdKeyedVector.push_back(keyValue); } return stdKeyedVector; }42.2.7 DrmSessionManager — 资源管理DrmSessionManager 位于frameworks/av/drm/libmediadrm/DrmSessionManager.cpp,管理 DRM 会话生命周期,对接 Android ResourceManagerService,系统资源紧张时支持会话回收。会话打开时,管理器向 ResourceManagerService 注册 kDrmSession 类型资源。// 摘自 frameworks/av/drm/libmediadrm/DrmSessionManager.cpp static std::vectorMediaResourceParcel toResourceVec( const Vectoruint8_t sessionId, int64_t value) { using Type = aidl::android::media::MediaResourceType; using SubType = aidl::android::media::MediaResourceSubType; std::vectorMediaResourceParcel resources; MediaResourceParcel resource{ Type::kDrmSession, SubType::kUnspecifiedSubType, toStdVec(sessionId), value}; resources.push_back(resource); return resources; }当 DRM 会话资源耗尽(很多 DRM 实现限制并发会话数),ResourceManagerService 可以回收低优先级应用的会话;回调 DrmSessionManager 关闭会话,并向应用发送 EVENT_SESSION_RECLAIMED 事件。42.2.8 DRM 事件传递事件从 HAL 插件经过框架送达应用,依靠 IDrmPluginListener AIDL 回调接口和 DrmHalListener 类。DrmHalListener 把 AIDL 事件类型转为框架事件类型,每一类事件更新指标计数器。// 摘自 frameworks/av/drm/libmediadrm/DrmHalListener.cpp ::ndk::ScopedAStatus DrmHalListener::onEvent( EventTypeAidl eventTypeAidl, const std::vectoruint8_t sessionId, const std::vectoruint8_t data) { mMetrics-mEventCounter.Increment((uint32_t)eventTypeAidl); // ... 分发至IDrmClient ... }42.2.9 设备预配部分 DRM 方案要求设备预配:一次性流程,设备从预配服务器获取唯一凭证。流程与密钥请求‑响应模式类似:应用调用openSession()或getKeyRequest()捕获NotProvisionedException。调用getProvisionRequest()获取预配请求报文。将请求报文发送至预配服务器地址。调用provideProvisionResponse()传入服务器返回数据。HAL 接口签名:// 摘自 hardware/interfaces/drm/aidl/android/hardware/drm/IDrmPlugin.aidl ProvisionRequest getProvisionRequest( in String certificateType, in String certificateAuthority);42.2.10 安全停止(Secure Stops)安全停止用于实现并发播放数限制。每次创建 MediaCrypto 对象时,HAL 插件持久化带签名的会话记录。播放结束后,应用取出记录转发给许可证服务器,服务器校验会话确实已经终止。IDrmPlugin 提供完整安全停止生命周期接口:方法用途getSecureStops()获取全部安全停止记录getSecureStopIds()获取全部安全停止 IDgetSecureStop(SecureStopId)按 ID 读取单条记录releaseSecureStops(OpaqueData)携带服务器确认释放记录releaseSecureStop(SecureStopId)释放指定单条releaseAllSecureStops()释放全部removeSecureStop(SecureStopId)不经过服务器确认直接删除removeAllSecureStops()直接删除全部42.2.11 离线许可证管理离线许可证允许无网络条件播放内容。框架提供接口管理离线许可证状态。// 离线许可证关键流程 // 1. 请求离线密钥 KeyRequest request = mediaDrm.getKeyRequest( sessionId, initData, mimeType, MediaDrm.KEY_TYPE_OFFLINE, null); // 2. 传入响应后拿到keySetId byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, response); // 3. 后续恢复离线密钥到新会话 mediaDrm.restoreKeys(newSessionId, keySetId); // 4. 查询离线许可证状态 ListKeySetId offlineKeys = mediaDrm.getOfflineLicenseKeySetIds(); OfflineLicenseState state = mediaDrm.getOfflineLicenseState(keySetId);IDrmPlugin HAL 定义三种离线许可证状态:aidl// 摘自 hardware/interfaces/drm/aidl/android/hardware/drm/OfflineLicenseState.aidl enum OfflineLicenseState { UNKNOWN, // 无法判定状态 USABLE, // 密钥可用于解密 INACTIVE, // 标记待释放,但尚未确认 }42.2.12 插件路径解析传统基于动态库的插件,运行时从厂商指定路径加载。// 摘自 frameworks/av/drm/libmediadrm/DrmPluginPath.cpp const char* getDrmPluginPath() { char value[PROPERTY_VALUE_MAX]; if (property_get("drm.64bit.enabled", value, NULL) == 0) { return "/vendor/lib/mediadrm"; } else { return "/vendor/lib64/mediadrm"; } }