前言
Android 设备的锁屏密码(PIN、密码、图案)是用户数据保护的第一道防线。理解其存储和验证机制,不仅对手机取证和安全评估至关重要,也是理解 Android 安全架构中 Hardware-Backed Keystore 设计思想的重要切入点。本文将深入分析 Android 锁屏密码的存储格式、加密算法和验证流程,涵盖从 Java 层 LockSettingsService 到 TEE 层 GateKeeper TA 的完整调用链。
密码存储的演进
在 Android 早期版本(Android 4.4 之前),锁屏密码的 hash 值存储在 /data/system/password.key 中,使用简单的 MD5 + SHA1 组合,存在彩虹表攻击风险。其格式为:
MD5(salt + SHA1(salt + password)) |
salt 为 64 位随机值,存储在前 8 字节中。这种格式由于没有硬件保护,可以通过彩虹表离线破解,且计算成本低(每秒可尝试数百万次),在设备丢失后极不安全。
从 Android 5.0 开始,Google 引入了 Gatekeeper 机制,将密码验证转移到 TEE(Trusted Execution Environment)中执行,大幅提升安全性。对应的存储文件也发生了变化:
| 文件路径 | 用途 |
|---|---|
/data/system/gatekeeper.password.key |
PIN/密码的加密数据 |
/data/system/gatekeeper.pattern.key |
图案的加密数据 |
/data/misc/gatekeeper/ |
Gatekeeper 持久化状态 |
从 Android 6.0 开始,Gatekeeper 进一步增强了与 Keymaster 的集成,要求所有密码派生和 hash 验证都必须在 TEE 内完成。Android 7.0 引入了基于文件的加密(FBE,File-Based Encryption),其中锁屏密码与磁盘加密密钥直接关联。Android 9.0 引入了防回滚和更强的度量验证。Android 12 引入 Keystore 2.0,将 Gatekeeper 的 SID(Secure Identifier)与 Keystore auth token 机制更紧密地绑定。
相关 AOSP 源码路径:
| 模块 | 路径 |
|---|---|
| LockSettingsService | frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java |
| LockSettingsStorage | frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java |
| gatekeeperd | system/gatekeeper/gatekeeperd.cpp |
| Gatekeeper HAL | hardware/libhardware/include/hardware/gatekeeper.h |
| Keymaster HAL | hardware/libhardware/include/hardware/keymaster_defs.h |
| scrypt 实现 | external/scrypt/ |
| Keystore 2.0 | system/security/keystore2/ |
| SyntheticPasswordManager | frameworks/base/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java |
| vold (磁盘加密) | system/vold/ |
| 密码策略管理 | frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java |
Gatekeeper 架构概述
Gatekeeper 分为三层,形成完整的可信执行链:
Java 层 (LockSettingsService) |
详细分析每一层:
Java 层:LockSettingsService
LockSettingsService(简称 LSS)是锁屏密码管理的核心 Java 服务,运行在 system_server 进程中。它通过 Binder 向外部提供 ILockSettings 接口,其调用的主要方法包括:
setLockCredential(credential, type, savedCredential, quality)— 设置或修改锁屏密码verifyCredential(credential, type, challenge)— 验证锁屏密码checkCredential(credential, type, userId)— 检查密码是否正确(不改变状态)
以 verifyCredential 为例,其完整调用链为:
LockSettingsService.verifyCredential() |
AOSP 关键源码位置:
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java |
Native 层:gatekeeperd
gatekeeperd 是 /system/bin/gatekeeperd,作为 system 服务运行。它负责:
- 接收来自 LSS 的 Binder 调用
- 连接 GateKeeper HAL(
hw_get_module(HAL_GATEKEEPER_ID)) - 从文件中读取/写入 handle 数据
- 在验证失败时实施指数退避延迟
gatekeeperd 的关键数据结构:
// system/gatekeeper/gatekeeperd.cpp |
TEE 层:Gatekeeper TA
Gatekeeper TA 运行在 TEE(如 Qualcomm QSEE、ARM TrustZone)中,是一个完全隔离于 Android 操作系统的执行环境。TA 拥有:
- 硬件唯一密钥(HUK, Hardware Unique Key):在芯片制造时熔断写入,不可读取
- HMAC 签名能力:使用 HUK 对密码派生结果进行签名,生成不可伪造的 handle
- 安全的单调计数器:用于防重放(anti-replay)和 auth token 的时效性验证
- 隔离的执行空间:Android OS 甚至 Linux 内核都无法访问 TA 的内存
Gatekeeper HAL 接口定义
完整 HAL 接口定义在 hardware/libhardware/include/hardware/gatekeeper.h:
typedef struct gatekeeper_device { |
Android 8.0+ 引入了 HAL 模式下的 HIDL 接口(android.hardware.gatekeeper@1.0),AOSP 路径:
hardware/interfaces/gatekeeper/1.0/IGatekeeper.hal |
返回值定义:
GATEKEEPER_RESPONSE_OK = 0 // 验证成功 |
密码文件格式详解
gatekeeper.password.key / gatekeeper.pattern.key
这两个文件并不直接存储密码的 hash,而是存储经过 Gatekeeper 处理后的 密文签名句柄(password handle)。文件格式如下:
offset size field |
handle 的实际二进制内容由 GateKeeper TA 在 TEE 内使用 HUK 派生密钥加密。加密通常使用 AES-256-GCM,确保:
- 机密性:没有 HUK 无法解密 handle 内容
- 完整性:GCM 的认证标签保证 handle 未被篡改
- 唯一性:每次 enroll 即使使用相同密码,生成的 handle 也完全不同(GCM 的随机 IV)
handle 解密后的内部结构(仅在 TEE 中可见):
+------------------+ |
注意:这里的 handle 并不是简单的 hash,它由 Gatekeeper TA 在 TEE 中使用硬件派生密钥加密。因此在没有 TEE 密钥的情况下,即使获取了 gatekeeper 文件也无法离线破解密码。
LockSettingsStorage 读取逻辑(AOSP 源码参考):
LockSettingsStorage.java 中的 readCredentialHash() 方法负责从磁盘读取 handle:
// frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java |
老版本格式(兼容性参考)
在没有 Gatekeeper 的老设备上,password.key 格式为:
offset size field |
sail 为 64 位随机值,存储在前 8 字节中。这种格式由于没有硬件保护,可以通过彩虹表离线破解。
Android 9.0+ 的 Synthetic Password
从 Android 9.0 开始,Google 引入了 Synthetic Password 机制(AOSP 路径:frameworks/base/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java),进一步将锁屏密码与磁盘加密密钥(FBE)绑定:
用户密码 |
Synthetic Password 的关键特点是:
- SP 本身从不存储,只在验证密码成功后的短时间内存在于内存中
- SP 的生成依赖多个因子:用户密码 + Gatekeeper handle + 设备绑定密钥
- 修改密码不会重新加密整个磁盘:只需用旧 SP 解密 DEK,再用新 SP 加密即可
SyntheticPasswordManager 的核心数据结构:
// SyntheticPasswordManager.java 中的关键 Token |
scrypt 密钥派生函数
Gatekeeper 使用 scrypt(发音 “ess crypt”)作为密码派生的核心算法。scrypt 是一种内存硬性(memory-hard)的 KDF,专门设计用于对抗暴力破解——因为每次派生需要大量内存,使得 GPU/ASIC 并行攻击成本极高。
scrypt 的完整实现在 AOSP 的 external/scrypt/ 目录下:
external/scrypt/ |
scrypt 的函数签名:
int crypto_scrypt(const uint8_t *passwd, size_t passwdlen, |
参数含义:
- N (65536 ~ 1048576 = 2^16 ~ 2^20):CPU/内存成本因子,必须为 2 的幂。值越大越安全但越慢。
- r (通常为 8):块大小因子,增大 r 增加对每个密码猜测的内存需求。
- p (通常为 1):并行度因子,增大 p 增加并行猜测的成本。
- salt (16 bytes):随机盐值,防止彩虹表攻击。
内存使用量近似公式:128 × r × N 字节。
实例:N=2^16, r=8 → 128 × 8 × 65536 = 67,108,864 字节 ≈ 64 MB / 次猜测。
这意味着攻击者想要每秒尝试 1000 个密码,就需要 64 GB 的内存带宽——对于传统 GPU(通常只有数 GB 显存)来说极不现实,即使对于 ASIC 也需要极高的片上 SRAM 成本。
scrypt 算法内部的四步过程:
- PBKDF2-SHA256(第一轮):password + salt → 生成初始块 B(p × r × 128 字节)
- BlockMix:对 B 进行 r 轮混合,利用 Salsa20/8 核心作为混搅函数
- ROMix(内存硬性核心):进行 N 次 BlockMix,每次结果存入内存(这正是 N × r × 128 字节的来源),后续的混搅随机访问已存储的块
- PBKDF2-SHA256(第二轮):password + 第3步结果 → 最终派生密钥
Android 中 scrypt 在 Gatekeeper 的初始实现(Android 5.0/5.1)中用于软件层密码派生;从 Android 6.0 开始,密码派生被移入 TEE,scrypt 参数由 TA 决定,宿主机无法直接干预。TEE 内部的 scrypt 实现通常由芯片厂商提供并经过安全审计。
Gatekeeper 验证流程
完整的锁屏密码验证流程如下:
用户输入密码 |
关键点:密码原文永远不会离开 TEE,Android 用户空间和 Linux 内核都无法接触到明文密码或派生密钥。
verifyCredential 详细代码路径
// frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java |
Auth Token 机制
验证成功后,Gatekeeper TA 生成一个 auth token,格式为:
+------------------+ |
Auth token 由 TimedAuthenticationToken 封装(Keystore 2.0 中):
system/security/keystore2/src/raw_device.cpp — createAuthToken() |
Keystore 在使用 auth token 进行密钥授权时会验证:
- HMAC 完整性校验
- 时间戳是否在有效期(通常 5-10 分钟)内
- challenge 是否匹配(防重放)
- authenticator type 是否与密钥的需求匹配
时序攻击防护
Gatekeeper 在验证失败时会引入递增延迟:
// gatekeeperd 逻辑(简化) |
这有效防止了暴力破解:失败 5 次后等待 30 × 2^4 = 480 秒 = 8 分钟;失败 10 次后等待 30 × 2^9 = 15360 秒 ≈ 4.3 小时。
此外,Gatekeeper TA 内部使用 恒定时间比较(constant-time comparison)来防止侧信道时序攻击:
// 恒定时间比较(简化) |
无论 hash 在第几个字节不匹配,循环总是遍历全部字节,比较操作耗时始终相同。
图案锁的存储
图案锁(Pattern Lock)的记录方式与 PIN/密码不同。图案被编码为一个字节序列,代表网格中的点连接顺序(3x3 网格,编号 1-9)。
3x3 网格的点编号:
1 2 3 |
例如图案 “L 形” 编码为 [1, 4, 7, 8, 9]。LockPatternUtils 中的编码逻辑:
// frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java |
然后对该字节序列进行与密码相同的处理流程:
pattern_bytes → SHA-256(pattern_bytes) → scrypt(hash, salt, N, r, p) → Gatekeeper TA 加密签名 |
图案锁先做了 SHA-256 哈希是为了将变长序列(图案最少 4 个点、最多 9 个点)转为定长输入。
图案的最大可能性数(最少 4 点、最多 9 点、每个点最多使用一次):
| 点数 | 排列数 |
|---|---|
| 4 | ≥ 1624 |
| 5 | ≥ 7152 |
| 6 | ≥ 26016 |
| 7 | ≥ 72912 |
| 8 | ≥ 140704 |
| 9 | 140704 (9! 但受限于相邻规则) |
相比 4 位 PIN(10000 种可能)或 6 位 PIN(100 万种),图案锁的暴力空间更大,但实际中用户常选择简单图案(如 Z 形、L 形),通过肩窥(shoulder surfing)易于破解。
与 Hardware-Backed Keystore 的集成
Android Keystore 2.0(Android 12+,AOSP 路径:system/security/keystore2/)将 Gatekeeper 与 Keystore 更紧密地结合:
SID 机制
- 用户设置锁屏密码 → Gatekeeper enroll → 生成 SID(Secure Identifier,64-bit 随机值)
- SID 被封装在 password handle 中,随 handle 一起存储
- Keystore 中的 auth-bound 密钥在生成时会绑定到当前 SID
- 每次验证密码成功后,Gatekeeper 输出 SID 和 auth token
- Keystore 用此 SID 验证 auth token 的合法性
- auth token 带有时间戳和 HMAC(用 Keystore 和 Gatekeeper 共享的密钥签名),用于授权 Keystore 操作
- 这个机制确保:只有知道锁屏密码的人才能使用设备上的加密密钥
Key Authorization 流程
Keystore 2.0 的密钥授权检查流程(简化):
应用请求使用密钥 |
AOSP 相关源码:
system/security/keystore2/src/authorization.cpp — 授权检查实现 |
这就解释了为什么修改锁屏密码后,某些应用的加密数据会丢失——因为 Gatekeeper enroll 会生成新的 SID(除非使用 SyntheticPassword 的 re-enroll 路径),旧的 auth-bound 密钥再也无法通过 SID 匹配验证,从而永久失效。Android 9.0+ 的 SyntheticPassword 部分解决了这个问题(支持 re-enroll to new password 而不改变 DEK),但应用层 auth-bound 密钥仍可能受影响。
生物识别与 Gatekeeper 的集成
指纹和面部识别与 Gatekeeper 的协作机制:
- 用户解锁设备 → BiometricPrompt 调用
BiometricService.authenticate() - Biometric HAL 验证指纹/面部成功后,调用 GateKeeper 的 verify 接口
- GateKeeper TA 生成 auth token(类型标识为 Fingerprint 或 Face)
- auth token 传递给 Keystore,允许使用 biometric-bound 密钥
- 注意:首次启动或 72 小时未解锁后,biometric auth token 失效,强制要求密码验证
AOSP 相关源码:
frameworks/base/services/core/java/com/android/server/biometrics/BiometricService.java |
安全评估与取证
获取密码的难度评估
| 场景 | 难度 | 说明 |
|---|---|---|
| 肩窥/涂抹痕迹 | 低 | 物理观察图案或 PIN |
| 老设备 (4.4-) | 中 | 离线彩虹表攻击 password.key |
| 现代设备 + root | 高 | 可读取 gatekeeper 文件但无法离线破解(需 TEE 密钥) |
| 现代设备 - root | 极高 | 无法读取 gatekeeper 文件,需硬件攻击 TEE |
| FBE 启用 + 关机 | 几乎不可能 | DEK 被 wrapped,解锁需要密码进入 TEE |
暴力破解的数学分析
假设攻击者可以以 10^6 hash/s 的速度尝试(软件 scrypt,N=2^16 约 64MB/次):
- 4 位 PIN(10000 种):0.01 秒
- 6 位 PIN(1000000 种):1 秒
- 8 位字母数字密码(~2.8×10^14 种):约 8892 年
加上 gatekeeperd 的指数退避延迟后,即使是最简单的 4 位 PIN 也需要数小时才能完成在线暴力破解(每次失败后延迟递增),远远超出抓取设备的时间窗口。
面试常考问题
Q1: Android 是如何存储锁屏密码的?是否可以直接从文件中读取密码原文?
A: Android 从 5.0 开始使用 Gatekeeper 机制存储密码。密码原文不会存储在任何地方。/data/system/gatekeeper.password.key 中存储的是一个加密句柄(handle),包含 salt、scrypt 参数和经过 TEE 中硬件密钥签名后的 hash。由于签名密钥存储在 TEE 的硬件安全区域(HUK/Hardware Unique Key),即使获取了 root 权限也无法直接提取密钥来离线破解密码。攻击者在拥有 TEE 密钥的情况下,仍需通过暴力破解(每次猜测都需要 scrypt 计算),而 scrypt 的内存硬性特征(~64MB/次)使得 GPU/ASIC 加速效果有限。关键 AOSP 源码路径:system/gatekeeper/gatekeeperd.cpp、hardware/libhardware/include/hardware/gatekeeper.h、frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java。
Q2: scrypt 与 PBKDF2、bcrypt 相比有什么优势?为什么 Android 选择 scrypt?
A: scrypt 是内存硬性(memory-hard)的 KDF,每次派生需要分配大量内存(通常 16-64 MB)。PBKDF2 和 bcrypt 主要是 CPU 硬性,通过增加迭代次数提高成本,但可以使用 GPU/ASIC 大规模并行攻击——因为它们不需要大量内存。而 scrypt 的 memory-hard 特性意味着:攻击者无论使用多么强大的 GPU,都必须为每个密码猜测分配同样大小的内存,这在硬件成本上形成了硬性限制。以密码破解常见的 ASIC 设计为例,要在单片芯片上并行处理 1000 个 scrypt 猜测(64MB/次),需要 64GB 的片上 SRAM,这在物理和商业上都不可行。Android 选择 scrypt 正是出于对抗大规模离线破解的考虑,尤其在移动设备取证场景中尤为重要。AOSP 实现路径:external/scrypt/lib/crypto/crypto_scrypt.c。
Q3: Gatekeeper 和 Keymaster 是什么关系?它们如何协作保护用户数据?
A: Gatekeeper 负责密码验证和 SID 生成;Keymaster 负责密钥管理和加密操作。协作流程:(1) 用户设置锁屏密码 → Gatekeeper TA 在 TEE 中 enroll 密码并生成 SID;(2) 应用创建 auth-bound 密钥时,Keymaster 将该密钥绑定到当前 SID;(3) 解锁验证时,Gatekeeper 验证密码成功后输出 SID 和 auth token;(4) Keymaster 用此 SID 验证 auth token 的合法性(验证 HMAC、时间戳、challenge);(5) 应用执行 Keystore 操作时,Keymaster 通过 auth token 检查授权。这个架构确保即使攻击者拿到设备的完整文件系统镜像,在没有正确锁屏密码的情况下也无法使用硬件保护的密钥。AOSP 关键路径:system/security/keystore2/src/auth_token_manager.cpp、system/security/keystore2/src/authorization.cpp、system/gatekeeper/gatekeeperd.cpp。
Q4: 什么是 Synthetic Password?它解决了什么问题?
A: Synthetic Password(SP)是 Android 9.0 引入的密码保护方案(AOSP:frameworks/base/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java),解决了两大问题:(1) 修改锁屏密码不再需要重新加密整个磁盘——因为实际加密磁盘的 DEK 由 SP 保护,修改密码时只需用旧 SP 解密 DEK、用新 SP 重新加密,IO 操作是常数时间;(2) 多用户支持——每个用户有独立的 SP 和密钥链,互不干扰。SP 的生成依赖多个因子(用户密码 + Gatekeeper handle + 设备绑定密钥),使得任何单点(如只盗取 password key 文件)都无法重建 SP。
Q5: 锁屏密码的延迟退避机制如何工作?能否通过重启绕过?
A: gatekeeperd 在验证失败后采用指数退避:30ms × 2^(失败次数)。失败次数存储在 /data/misc/gatekeeper/ 的持久化文件中,重启不会重置。当失败次数达到 DevicePolicyManager 配置的阈值(默认无限制,但设备管理员可设置为 5-20 次),系统自动执行 wipeData(0) 恢复出厂设置。退避延迟在 gatekeeperd 的 Native 层实现(system/gatekeeper/gatekeeperd.cpp),不受 Java 层的生命周期影响,因此应用被杀死后重启仍然继承之前的失败计数。
参考
- AOSP:
system/gatekeeper/gatekeeperd.cpp— gatekeeperd 守护进程实现 - AOSP:
hardware/libhardware/include/hardware/gatekeeper.h— Gatekeeper HAL 接口定义 - AOSP:
hardware/interfaces/gatekeeper/1.0/IGatekeeper.hal— HIDL GateKeeper 接口 - AOSP:
system/keymaster/— Keymaster HAL 实现 - AOSP:
system/security/keystore/— Keystore 1.0 - AOSP:
system/security/keystore2/— Keystore 2.0 (Android 12+) - AOSP:
external/scrypt/— Android 使用的 scrypt 实现 - AOSP:
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java— 密码设置/验证的 Java 层入口 - AOSP:
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java— handle 文件的读写逻辑 - AOSP:
frameworks/base/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java— Synthetic Password 实现 - AOSP:
frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java— 图案锁工具类 https://www.tarsnap.com/scrypt.html— scrypt 算法论文与原始实现



