
一、BatteryStatusAPI的起源、设计初衷与隐私争议BatteryStatusAPI是W3C设备API工作组早期提出的一项浏览器接口规范于2012年左右进入草案阶段。其设计初衷是帮助网页开发者根据设备的电池状态优化应用行为。例如当检测到设备电量低于15%且未充电时网页可以自动降低视频播放码率、减少动画帧率、暂停后台同步任务或者弹出提示引导用户连接充电器。这种根据电量自适应调整的能力对于移动端Web应用和渐进式Web应用具有实际价值。该 API 的具体使用方式是通过navigator.getBattery方法返回一个Promise对象解析后获得BatteryManager实例。BatteryManager包含以下只读属性charging布尔值表示设备是否正在连接电源充电。chargingTime数字表示预计充满电所需的剩余秒数。如果已经充满或未充电此值为0。dischargingTime数字表示当前电量下预计还能使用的剩余秒数。如果正在充电此值为Infinity。level浮点数范围0到1表示剩余电量百分比例如0.75表示75%。此外BatteryManager还定义了四个事件chargingchange、chargingtimechange、dischargingtimechange和levelchange当对应属性发生变化时触发。网页可以注册这些事件的监听器实时响应电池状态的改变。然而该API上线不久就被隐私研究人员发现存在严重的指纹泄露风险。2015年西班牙马德里卡洛斯三世大学的研究团队发表论文详细阐述了BatteryStatusAPI如何被用于追踪用户。研究者发现电池电量和充电状态的组合具有足够的熵值来区分设备——即使在同型号设备上由于电池老化程度、充电习惯、使用模式的差异电量和剩余时间的组合也呈现出随机性。实验数据显示仅使用level和dischargingTime两个参数的组合就可以在约1400万用户中唯一识别出超过99%的设备。这一发现引发了浏览器厂商的迅速反应。Mozilla在Firefox52版本中限制了对chargingTime和dischargingTime的访问将其四舍五入到最接近的分钟级别。Google在Chrome88版本中彻底限制了该API不再提供精确的chargingTime和dischargingTimelevel也被限制为只有0、0.5、1 三个离散值。Apple在Safari 中没有实现完整的BatteryStatusAPI且在iOS上完全禁用了该功能。Edge跟随Chromium的决策。尽管主流浏览器已经大幅限制或弃用了该 API但在某些特定环境下——例如基于旧版Chromium内核的定制浏览器、企业内部分发的Web应用、或者用户手动启用了实验性标志的浏览器中——该API仍然可以被用于指纹采集。此外一些风控系统可能会检查浏览器是否实现了getBattery方法如果一个浏览器声称是最新版本却缺少这个方法或者方法签名异常也可能被视为可疑信号。因此对于追求完整环境指纹模拟的指纹浏览器而言正确处理BatteryStatusAPI仍然是一个不可忽视的细节。像中屹指纹浏览器这类产品在环境配置中包含了电池状态的模拟选项允许用户为每个环境独立设置电量行为。二、BatteryStatusAPI的底层数据获取路径与Chromium实现剖析要有效地伪造电池状态首先需要理解Chromium中该API 的完整数据流。以下以Windows平台为例详细说明从操作系统底层到JavaScript层的传输过程。在 Windows 系统中电池信息通过GetSystemPowerStatusAPI 获取。该函数定义在winbase.h中属于Windows系统核心服务的一部分。GetSystemPowerStatus接收一个SYSTEM_POWER_STATUS结构体指针填充以下字段ACLineStatus0 表示离线电池供电1 表示在线交流电源255 表示未知。BatteryFlag电池状态标志例如 1-高电量2-低电量4-临界电量8-充电中128-无电池等。BatteryLifePercent剩余电量百分比0-100255 表示未知。BatteryLifeTime剩余使用秒数-1 表示未知-2 表示正在充电。BatteryFullLifeTime充满电所需秒数-1 表示未知-2 表示未充电。Chromium 的base模块中有一个PowerMonitor类专门负责监听系统电源状态变化。在 Windows 上PowerMonitor通过注册WM_POWERBROADCAST窗口消息来接收电池事件同时定期调用GetSystemPowerStatus轮询状态。当检测到变化时PowerMonitor会通知上层的BatteryMonitor类。BatteryMonitor位于content/browser/battery/battery_monitor_impl.cc。它是浏览器进程中的一个服务负责响应渲染进程中网页发起的getBattery请求。当渲染进程调用getBattery()时实际发生的是一个跨进程的 Mojo 调用。渲染进程向浏览器进程发送一个BatteryMonitor.GetBatteryStatus请求浏览器进程中的BatteryMonitorImpl::QueryNextStatus方法被触发。该方法通过PowerMonitor获取当前电池状态然后构造一个BatteryStatus结构体包含charging、chargingTime、dischargingTime、level四个字段通过 Mojo 管道返回给渲染进程。渲染进程收到响应后BatteryManager对象更新其内部属性并触发对应的change事件。整个过程是异步的因此getBattery返回的是一个 Promise。从以上数据流可以看出要在 Chromium 层面伪造电池状态最直接的方法是修改BatteryMonitorImpl::QueryNextStatus方法让它在返回数据时不再调用PowerMonitor而是读取一个预设的配置文件中的值。这种修改需要重新编译 Chromium但优点是修改位置非常集中且对上层完全透明——从渲染进程的 JavaScript 代码来看所有 API 行为与原生实现完全一致没有任何篡改痕迹。另一种更轻量级的方案是在 JavaScript 层面做注入。指纹浏览器在每次创建新环境时向页面注入一段脚本这段脚本会检测navigator.getBattery的存在然后用一个自定义函数替换它。自定义函数返回一个伪造的 Promise这个 Promise 解析出的BatteryManager对象的所有属性和事件也都是伪造的。这种方案的优点是无需重新编译浏览器可以快速迭代但缺点是有可能被风控脚本检测到因为风控脚本可以检查getBattery.toString()是否返回原生代码字符串或者通过其他方式验证函数的完整性。三、动态电量状态模拟的有限状态机设计与实现伪造电池状态不能简单地使用固定值。一个永远显示 100% 电量且永远在充电中的浏览器环境在逻辑上是不合理的——真实用户不可能永远不消耗电量也不可能永远连接电源。同时如果电池状态在不同的会话之间总是回到相同的初始值也会显得不自然。因此需要设计一个有限状态机来模拟电量的自然变化。状态机的核心设计要素包括状态定义State枚举包含两种主要状态CHARGING充电中和DISCHARGING放电中。充电状态又可细分为CHARGING_LOW低电量充电、CHARGING_NORMAL、CHARGING_NEAR_FULL等但简单的状态机不需要如此复杂。状态变量level当前电量浮点数范围 0.0 到 1.0。chargeRate充电速率单位 %/秒典型值 0.001 到 0.005即每 100 秒充 0.1% 到 0.5%。dischargeRate放电速率同样范围。lastUpdateTimestamp上次更新时间戳用于计算经过的时间。状态转移逻辑当state CHARGING时level随时间增加直到达到 1.0 后自动切换到FULL子状态充电停止或转为涓流充电。当state DISCHARGING时level随时间减少直到达到 0.0。通常真实设备不会达到 0.0 就会自动关机因此在模拟时可以在level低于 0.05 时随机决定是否切换到充电状态或保持放电直到关机。状态切换也可以由外部事件触发。例如可以模拟用户插拔电源线的行为在随机的时间间隔内如每 30-120 分钟以一定概率切换charging状态。剩余时间的计算chargingTime当state CHARGING时(1.0 - level) / chargeRate当level 1.0时0否则Infinity。dischargingTime当state DISCHARGING时level / dischargeRate当state CHARGING时Infinity。为了实现跨会话的状态持久化每个指纹环境需要在本地保存当前的电池状态。建议的存储格式是一个 JSON 对象包含以下字段level、charging、chargeRate、dischargeRate、lastUpdateTimestamp。每次环境启动时读取存储的状态根据当前时间与lastUpdateTimestamp的差值演化状态机得到最新的level和charging值然后再将新的状态保存回存储。这样模拟出来的电池状态具有连续性和历史记忆不会出现电量回跳或无故归零的异常。此外为了避免所有环境在同一时间点状态过于相似可以为每个环境随机生成初始参数。初始level建议在 0.2 到 0.95 之间均匀随机charging以 70% 的概率为true多数用户在使用设备时会连接电源chargeRate和dischargeRate可以在 0.001 到 0.008 之间随机选取。这些参数应当作为环境指纹的一部分随环境配置一起导出和导入以便在迁移到另一台机器后保持状态连续性。四、多环境下的电池状态隔离与跨维度一致性约束当在一台物理电脑上同时运行多个指纹浏览器环境时每个环境的电池状态必须完全独立。如果所有环境都同时充电或同时放电或者所有环境的电量变化步调一致平台可以通过对多个账号的交叉比对发现异常。实现独立性的关键是每个环境使用独立的持久化存储。Chromium 的用户数据目录Profile 目录天然提供了这种隔离——每个 Profile 有自己的 LocalStorage、IndexedDB 和文件系统。电池状态数据可以存储在每个 Profile 的偏好设置文件中或者单独存储在一个 JSON 文件中。指纹浏览器需要确保在环境启动时读取的是该环境专属的存储路径而不是全局共享的存储。跨维度一致性是另一个需要关注的方面。电池状态应当与指纹中设置的设备类型navigator.platform、userAgent中的设备标识保持松散的一致性如果环境模拟的是台式机Windows 或 macOS 的桌面版本台式机通常长期连接电源因此charging应该为truelevel接近 1.0dischargingTime为Infinity。如果模拟的是笔记本或移动设备则可以同时支持充电和放电两种状态。如果环境模拟的是手机User Agent 中包含Mobile关键字电池状态应该更加动态电量变化速率也可以稍快一些因为移动设备的功耗通常高于台式机。对于模拟服务器或云端环境的场景电池状态甚至可以完全省略即getBattery方法返回null或抛出异常但这种情况不太常见因为普通用户不会在服务器上运行浏览器。此外电池状态还可以与 Network Information API 产生间接关联。当设备电量低且未充电时某些操作系统会主动限制网络活动例如降低 WiFi 扫描频率或切换到更省电的移动网络模式。虽然这种关联非常弱但风控系统不太可能去验证这种深层次的一致性因此不需要过于严格地模拟。五、Battery Status API 指纹的检测技术与反反指纹对抗风控系统如果仍然依赖 Battery Status API通常采用以下几种检测策略单次快照页面加载时立即调用getBattery()一次记录level、charging、dischargingTime的组合。这种方法简单高效但获取的信息有限。多帧采样在几秒内多次调用getBattery()观察返回值的变化。真实设备的电池状态在短时间5秒内几乎不会变化而简单的随机伪装可能会每次返回不同的值从而暴露。因此伪造的电池状态在单次页面会话中必须保持稳定。状态机虽然会随时间演化但每秒的变化量极低0.001% 级别在几秒的采样窗口内可以视为恒定。事件监听测试脚本注册levelchange或chargingchange监听器然后通过某种方式尝试触发变化。如果监听器从不触发或者触发模式不符合预期也可能被检测。不过这种测试较为罕见因为触发条件不容易控制。跨 API 一致性校验将电池状态与其他 API 返回的信息做交叉验证。例如navigator.getBattery返回的chargingTime与系统的电源设置是否匹配这种验证在 Web 端几乎不可能实现因为网站无法访问操作系统的电源配置。但平台可以通过服务端的统计模型来发现异常如果大量来自不同 IP 的设备都报告完全相同的电池状态或者报告的状态与设备型号的典型状态不符就会触发警报。为了对抗这些检测指纹浏览器在模拟电池状态时需要注意以下几点每次新页面加载时的初始状态不应该完全随机而应该基于存储的历史状态演化而来保证连续性。不同环境的电池状态应该具有多样性避免出现大量环境共享相同或相近的状态值。如果风控系统已知某个特定地区的用户普遍使用笔记本并频繁充电可以在该地区的环境配置中适当提高charging的概率。六、主流浏览器的现状与未来趋势截至 2025 年Battery Status API 在主流浏览器中的状态如下Chrome/Edge默认情况下getBattery仍然可用但chargingTime和dischargingTime返回固定值0和Infinitylevel被四舍五入到最接近的离散值0、0.5、1。实际可用的信息只有charging和粗粒度的level。Firefox仍然支持但同样限制了精度。Safari从未完整实现在 iOS 和 macOS 上仅返回基本的安全信息。由于该 API 的指纹价值已经大大降低许多风控系统已经不再依赖它。但对于使用旧版 Chromium 内核的定制浏览器或者需要模拟特定旧环境的场景正确的电池状态模拟仍然有其意义。指纹浏览器可以将此功能作为一个可选的高级选项让用户决定是否需要启用。七、总结Battery Status API 代表了浏览器指纹技术中一个正在消失但仍然有趣的维度。它的核心价值不在于单独识别设备而在于作为辅助信号与其他指纹组合以及作为跨维度一致性的验证点。一个完善的指纹伪装方案需要对所有可能被采集的 API 进行全面的分析和处理即使是那些已经或即将被废弃的 API。因为风控系统的逻辑往往是多层次、多信号的忽略任何一个看似无关紧要的细节都可能累积成最终被识别的原因。