前言
Android 设备的锁屏密码(PIN、密码、图案)是用户数据保护的第一道防线。理解其存储和验证机制,不仅对手机取证和安全评估至关重要,也是理解 Android 安全架构中 Hardware-Backed Keystore 设计思想的重要切入点。本文将深入分析 Android 锁屏密码的存储格式、加密算法和验证流程。
密码存储的演进
在 Android 早期版本(Android 4.4 之前),锁屏密码的 hash 值存储在 /data/system/password.key 中,使用简单的 MD5 + SHA1 组合,存在彩虹表攻击风险。
从 Android 5.0 开始,Google 引入了 Gatekeeper 机制,将密码验证转移到 TEE(Trusted Execution Environment)中执行,大幅提升安全性。对应的存储文件也发生了变化:
| 文件路径 | 用途 |
|---|---|
/data/system/gatekeeper.password.key |
PIN/密码的加密数据 |
/data/system/gatekeeper.pattern.key |
图案的加密数据 |
/data/misc/gatekeeper/ |
Gatekeeper 持久化状态 |
Gatekeeper 架构概述
Gatekeeper 分为三层:
Java 层 (LockSettingsService) |
- gatekeeperd (
/system/bin/gatekeeperd):运行在 Android 用户空间的守护进程,负责接收 LockSettingsService 的验证请求。 - Gatekeeper HAL(
hardware/libhardware/include/hardware/gatekeeper.h):定义 HAL 接口(enroll / verify)。 - Keymaster HAL:与 Gatekeeper 协作,提供硬件支持的密钥操作。
密码文件格式详解
gatekeeper.password.key / gatekeeper.pattern.key
这两个文件并不直接存储密码的 hash,而是存储经过 Gatekeeper 处理后的 密文签名句柄(password handle)。文件格式如下:
offset size field |
注意:这里的 handle 并不是简单的 hash,它由 Gatekeeper TA 在 TEE 中使用硬件派生密钥加密,包含:
- scrypt / PBKDF2 派生参数(N、r、p 值,以及 salt)
- 密码的键控 hash(HMAC 使用硬件密钥签名的 hash)
因此在没有 TEE 密钥的情况下,即使获取了 gatekeeper 文件也无法离线破解密码。
老版本格式(兼容性参考)
在没有 Gatekeeper 的老设备上,password.key 格式为:
MD5(salt + SHA1(salt + password)) |
salt 为 64 位随机值,存储在前 8 字节中。这种格式由于没有硬件保护,可以通过彩虹表离线破解。
scrypt 密钥派生函数
Gatekeeper 使用 scrypt(发音 “ess crypt”)作为密码派生的核心算法。scrypt 是一种内存硬性(memory-hard)的 KDF,专门设计用于对抗暴力破解——因为每次派生需要大量内存,使得 GPU/ASIC 并行攻击成本极高。
scrypt 的函数签名:
int scrypt(const uint8_t *passwd, size_t passwdlen, |
参数含义:
- N (65536 ~ 1048576):CPU/内存成本因子,必须为 2 的幂。值越大越安全但越慢。
- r (通常为 8):块大小因子。
- p (通常为 1):并行度因子。
- salt:随机盐值,防止彩虹表攻击。
Android 中 scrypt 在 Gatekeeper 的初始实现(Android 5.0/5.1)中用于软件层密码派生;从 Android 6.0 开始,密码派生被移入 TEE,scrypt 参数由 TA 决定,宿主机无法直接干预。
Gatekeeper 验证流程
完整的锁屏密码验证流程如下:
用户输入密码 |
关键点:密码原文永远不会离开 TEE,Android 用户空间和 Linux 内核都无法接触到明文密码或派生密钥。
时序攻击防护
Gatekeeper 在验证失败时会引入递增延迟:
// gatekeeperd 逻辑(简化) |
此外,Gatekeeper 会使用 恒定时间比较 来防止侧信道时序攻击,即无论 hash 在第几个字节不匹配,比较操作耗时始终相同。
图案锁的存储
图案锁(Pattern Lock)的记录方式与 PIN/密码不同。图案被编码为一个字节序列,代表网格中的点连接顺序(3x3 网格,编号 1-9)。例如图案 “L 形” 编码为 [1, 4, 7, 8, 9],然后对该字节序列进行与密码相同的处理流程:
pattern = [1, 4, 7, 8, 9] |
与 Hardware-Backed Keystore 的集成
Android Keystore 2.0(Android 12+)将 Gatekeeper 与 Keystore 更紧密地结合:
- 用户设置锁屏密码 → Gatekeeper enroll → 生成 SID(Secure Identifier)
- Keystore 中的 auth-bound 密钥在生成时会绑定到当前 SID
- 每次验证密码成功后,Gatekeeper 输出有效的 SID,Keystore 用此 SID 生成 auth token
- auth token 带有时间戳和 MAC,用于授权 Keystore 操作
- 这个机制确保:只有知道锁屏密码的人才能使用设备上的加密密钥
这就解释了为什么修改锁屏密码后,某些应用的加密数据会丢失——因为 SID 变化,旧的 auth-bound 密钥无法再被授权访问。
面试常考问题
Q1: Android 是如何存储锁屏密码的?是否可以直接从文件中读取密码原文?
A: Android 从 5.0 开始使用 Gatekeeper 机制存储密码。密码原文不会存储在任何地方。/data/system/gatekeeper.password.key 中存储的是一个加密句柄(handle),包含 salt、scrypt 参数和经过 TEE 中硬件密钥签名后的 hash。由于签名密钥存储在 TEE 的硬件安全区域,即使获取了 root 权限也无法直接提取密钥来离线破解密码。攻击者在拥有 TEE 密钥的情况下,仍需通过暴力破解(每次猜测都需要 scrypt 计算),而 scrypt 的内存硬性特征使得 GPU/ASIC 加速效果有限。
Q2: scrypt 与 PBKDF2、bcrypt 相比有什么优势?为什么 Android 选择 scrypt?
A: scrypt 是内存硬性(memory-hard)的 KDF,每次派生需要分配大量内存(通常 16-64 MB)。PBKDF2 和 bcrypt 主要是 CPU 硬性,通过增加迭代次数提高成本,但可以使用 GPU/ASIC 大规模并行攻击——因为它们不需要大量内存。而 scrypt 的 memory-hard 特性意味着:攻击者无论使用多么强大的 GPU,都必须为每个密码猜测分配同样大小的内存,这在硬件成本上形成了硬性限制。Android 选择 scrypt 正是出于对抗大规模离线破解的考虑,尤其在移动设备取证场景中尤为重要。
Q3: Gatekeeper 和 Keymaster 是什么关系?它们如何协作保护用户数据?
A: Gatekeeper 负责密码验证和 SID 生成;Keymaster 负责密钥管理和加密操作。协作流程:(1) 用户设置锁屏密码 → Gatekeeper TA 在 TEE 中 enroll 密码并生成 SID;(2) 应用创建 auth-bound 密钥时,Keymaster 将该密钥绑定到当前 SID;(3) 解锁验证时,Gatekeeper 验证密码成功后输出 SID;(4) Keymaster 用此 SID 生成有时效性的 auth token;(5) 应用执行 Keystore 操作时,Keymaster 检查 auth token 的有效性。这个架构确保即使攻击者拿到设备的完整文件系统镜像,在没有正确锁屏密码的情况下也无法使用硬件保护的密钥。
参考
- AOSP:
system/gatekeeper/gatekeeperd.cpp— gatekeeperd 守护进程实现 - AOSP:
hardware/libhardware/include/hardware/gatekeeper.h— Gatekeeper HAL 接口定义 - AOSP:
system/keymaster/— Keymaster HAL 实现 - AOSP:
system/security/keystore/— Keystore 2.0 - AOSP:
external/scrypt/— Android 使用的 scrypt 实现 - AOSP:
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java— 密码设置/验证的 Java 层入口 https://www.tarsnap.com/scrypt.html— scrypt 算法论文与原始实现



