目录
  1. 1. 前言
  2. 2. 密码存储的演进
  3. 3. Gatekeeper 架构概述
    1. 3.1. Java 层:LockSettingsService
    2. 3.2. Native 层:gatekeeperd
    3. 3.3. TEE 层:Gatekeeper TA
  4. 4. Gatekeeper HAL 接口定义
  5. 5. 密码文件格式详解
    1. 5.1. gatekeeper.password.key / gatekeeper.pattern.key
    2. 5.2. 老版本格式(兼容性参考)
    3. 5.3. Android 9.0+ 的 Synthetic Password
  6. 6. scrypt 密钥派生函数
  7. 7. Gatekeeper 验证流程
    1. 7.1. verifyCredential 详细代码路径
    2. 7.2. Auth Token 机制
    3. 7.3. 时序攻击防护
  8. 8. 图案锁的存储
  9. 9. 与 Hardware-Backed Keystore 的集成
    1. 9.1. SID 机制
    2. 9.2. Key Authorization 流程
    3. 9.3. 生物识别与 Gatekeeper 的集成
  10. 10. 安全评估与取证
    1. 10.1. 获取密码的难度评估
    2. 10.2. 暴力破解的数学分析
  11. 11. 面试常考问题
  12. 12. 参考
【逆向安全技术-基础篇】锁屏密码加密算法分析

前言

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)
↓ Binder IPC
Native 层 (gatekeeperd 守护进程)
↓ HAL 接口 (hw_device_t)
TEE 层 (Gatekeeper TA — Trusted Application)

Hardware-Backed (HMAC key 由硬件保护,不可导出)

详细分析每一层:

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()
→ LockSettingsStorage.readCredentialHash() // 读取 gatekeeper.password.key 中的 handle
→ getGateKeeperService().verify(userId, challenge, oldHandle, enrolledHandle)
→ gatekeeperd.verify()
→ GateKeeperDevice::verify()
→ ioctl / hw_device_t->verify()
→ TEE GateKeeper TA

AOSP 关键源码位置:

frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java
Line ~932: verifyCredential()
Line ~1206: setLockCredential()
Line ~1568: getGateKeeperService() — 获取 gatekeeperd Binder 代理

Native 层:gatekeeperd

gatekeeperd/system/bin/gatekeeperd,作为 system 服务运行。它负责:

  1. 接收来自 LSS 的 Binder 调用
  2. 连接 GateKeeper HAL(hw_get_module(HAL_GATEKEEPER_ID)
  3. 从文件中读取/写入 handle 数据
  4. 在验证失败时实施指数退避延迟

gatekeeperd 的关键数据结构:

// system/gatekeeper/gatekeeperd.cpp
// GateKeeperProxy 封装了 HAL 操作
class GateKeeperProxy {
android::hardware::gatekeeper::V1_0::IGatekeeper* device;
uint32_t consecutiveFailureCount;
uint64_t lastFailureTimestamp;

int enroll(uint32_t uid, const uint8_t* currentHandle, uint32_t currentHandleSize,
const uint8_t* providedHandle, uint32_t providedHandleSize,
uint8_t** enrolledHandle, uint32_t* enrolledHandleSize);
int verify(uint32_t uid, uint64_t challenge,
const uint8_t* enrolledHandle, uint32_t enrolledHandleSize,
const uint8_t* providedHandle, uint32_t providedHandleSize,
uint8_t** authToken, uint32_t* authTokenSize);
};

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 {
struct hw_device_t common;

// 注册(设置)新密码
int (*enroll)(struct gatekeeper_device *dev, uint32_t uid,
const uint8_t *current_password_handle, uint32_t current_password_handle_length,
const uint8_t *current_password, uint32_t current_password_length,
const uint8_t *desired_password, uint32_t desired_password_length,
uint8_t **enrolled_password_handle, uint32_t *enrolled_password_handle_length);

// 验证密码
int (*verify)(struct gatekeeper_device *dev, uint32_t uid, uint64_t challenge,
const uint8_t *enrolled_password_handle, uint32_t enrolled_password_handle_length,
const uint8_t *provided_password, uint32_t provided_password_length,
uint8_t **auth_token, uint32_t *auth_token_length,
bool *request_reenroll);

// 删除用户
int (*delete_user)(struct gatekeeper_device *dev, uint32_t uid);

// 删除所有用户
int (*delete_all_users)(struct gatekeeper_device *dev);
} gatekeeper_device_t;

Android 8.0+ 引入了 HAL 模式下的 HIDL 接口(android.hardware.gatekeeper@1.0),AOSP 路径:

hardware/interfaces/gatekeeper/1.0/IGatekeeper.hal
hardware/interfaces/gatekeeper/1.0/default/GateKeeper.cpp

返回值定义:

GATEKEEPER_RESPONSE_OK          = 0   // 验证成功
GATEKEEPER_RESPONSE_RETRY = 1 // 需要稍后重试(失败延迟)
GATEKEEPER_RESPONSE_RESET = 2 // 错误计数器溢出,数据已擦除

密码文件格式详解

gatekeeper.password.key / gatekeeper.pattern.key

这两个文件并不直接存储密码的 hash,而是存储经过 Gatekeeper 处理后的 密文签名句柄(password handle)。文件格式如下:

offset  size   field
------ ---- -----
0x00 4 version (版本号)
0x04 4 type (0 = 密码/PIN, 1 = 图案)
0x08 4 handle_length (句柄长度)
0x0C N handle_data (加密句柄,包含 salt 和 hash 的密文)

handle 的实际二进制内容由 GateKeeper TA 在 TEE 内使用 HUK 派生密钥加密。加密通常使用 AES-256-GCM,确保:

  • 机密性:没有 HUK 无法解密 handle 内容
  • 完整性:GCM 的认证标签保证 handle 未被篡改
  • 唯一性:每次 enroll 即使使用相同密码,生成的 handle 也完全不同(GCM 的随机 IV)

handle 解密后的内部结构(仅在 TEE 中可见):

+------------------+
| version (4 bytes)| -- handle 格式版本
+------------------+
| flags (4 bytes) | -- 是否启用硬件签名等标志位
+------------------+
| type (4 bytes) | -- 0=密码 1=图案
+------------------+
| salt (16 bytes) | -- 随机盐值(scrypt 参数)
+------------------+
| scrypt N (8B) | -- N 值(CPU/内存因子)
+------------------+
| scrypt r (4B) | -- r 值(块大小)
+------------------+
| scrypt p (4B) | -- p 值(并行度)
+------------------+
| password hash | -- scrypt(密码, salt, N, r, p)
| (32 bytes) | -- 然后再用 HUK 派生的 HMAC key 签名
+------------------+
| auth token key | -- HMAC-SHA256(HUK, "auth_token_key")
| (32 bytes) |
+------------------+
| HMAC (32 bytes) | -- 整个 handle 的完整性校验
+------------------+

注意:这里的 handle 并不是简单的 hash,它由 Gatekeeper TA 在 TEE 中使用硬件派生密钥加密。因此在没有 TEE 密钥的情况下,即使获取了 gatekeeper 文件也无法离线破解密码。

LockSettingsStorage 读取逻辑(AOSP 源码参考):

LockSettingsStorage.java 中的 readCredentialHash() 方法负责从磁盘读取 handle:

// frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
public CredentialHash readCredentialHash(int userId) {
byte[] contents = readFile(getLockCredentialFilePathForUser(userId));
if (contents == null) return null;

int offset = 0;
final int version = LittleEndian.readInt(contents, offset);
offset += 4;

if (version < 3) {
// 老版本格式:直接存 hash
offset += 4; // type
byte[] hash = new byte[contents.length - offset];
System.arraycopy(contents, offset, hash, 0, hash.length);
return new CredentialHash(hash, version);
}

// 新版本格式:包含加密 handle
final int type = LittleEndian.readInt(contents, offset);
offset += 4;
final int handleLength = LittleEndian.readInt(contents, offset);
offset += 4;
byte[] handle = new byte[handleLength];
System.arraycopy(contents, offset, handle, 0, handleLength);

// 现在还需要读取 SecureSyntheticPassword 相关的数据...
return CredentialHash.create(handle, type, version);
}

老版本格式(兼容性参考)

在没有 Gatekeeper 的老设备上,password.key 格式为:

offset  size   field
------ ---- -----
0x00 8 salt (64-bit random)
0x08 20 MD5(salt + SHA1(salt + password))

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)绑定:

用户密码

Gatekeeper enroll → 生成 password handle

SyntheticPasswordManager → 生成 synthetic password(SP)

SP 加密 Disk Encryption Key

DEK 用于加密 /data 分区

Synthetic Password 的关键特点是:

  1. SP 本身从不存储,只在验证密码成功后的短时间内存在于内存中
  2. SP 的生成依赖多个因子:用户密码 + Gatekeeper handle + 设备绑定密钥
  3. 修改密码不会重新加密整个磁盘:只需用旧 SP 解密 DEK,再用新 SP 加密即可

SyntheticPasswordManager 的核心数据结构:

// SyntheticPasswordManager.java 中的关键 Token
class AuthenticationToken {
byte[] E0; // escrow token(托管令牌)
byte[] P1; // application token(应用令牌)
byte[] P2; // protector token(保护令牌)
}

class PasswordData {
byte[] passwordHandle; // Gatekeeper 返回的 handle
byte scryptN; // 简化的 N 参数
byte scryptR;
byte scryptP;
byte[] salt; // 16 bytes
}

scrypt 密钥派生函数

Gatekeeper 使用 scrypt(发音 “ess crypt”)作为密码派生的核心算法。scrypt 是一种内存硬性(memory-hard)的 KDF,专门设计用于对抗暴力破解——因为每次派生需要大量内存,使得 GPU/ASIC 并行攻击成本极高。

scrypt 的完整实现在 AOSP 的 external/scrypt/ 目录下:

external/scrypt/
lib/crypto/crypto_scrypt.c — 核心算法实现
lib/crypto/crypto_scrypt-ref.c — 参考实现
lib/crypto/sha256.c — SHA-256 基础原语
lib/util/memlimit.c — 内存限制检查

scrypt 的函数签名:

int crypto_scrypt(const uint8_t *passwd, size_t passwdlen,
const uint8_t *salt, size_t saltlen,
uint64_t N, uint32_t r, uint32_t p,
uint8_t *buf, size_t buflen);

参数含义:

  • 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 算法内部的四步过程:

  1. PBKDF2-SHA256(第一轮):password + salt → 生成初始块 B(p × r × 128 字节)
  2. BlockMix:对 B 进行 r 轮混合,利用 Salsa20/8 核心作为混搅函数
  3. ROMix(内存硬性核心):进行 N 次 BlockMix,每次结果存入内存(这正是 N × r × 128 字节的来源),后续的混搅随机访问已存储的块
  4. PBKDF2-SHA256(第二轮):password + 第3步结果 → 最终派生密钥

Android 中 scrypt 在 Gatekeeper 的初始实现(Android 5.0/5.1)中用于软件层密码派生;从 Android 6.0 开始,密码派生被移入 TEE,scrypt 参数由 TA 决定,宿主机无法直接干预。TEE 内部的 scrypt 实现通常由芯片厂商提供并经过安全审计。

Gatekeeper 验证流程

完整的锁屏密码验证流程如下:

用户输入密码

LockSettingsService.verifyCredential()
↓ Binder 调用 (ILockSettings.Stub)
gatekeeperd (native daemon, /system/bin/gatekeeperd)
↓ 读取 /data/system/gatekeeper.password.key
提取 handle(含 salt + scrypt 参数)
↓ HAL 调用 (hw_device_t->verify / HIDL IGatekeeper::verify)
Gatekeeper TA (TEE 中运行)

1. 使用 HUK 解密 handle
2. 从 handle 中恢复 salt 和 scrypt 参数
3. 对输入的密码执行 scrypt(password, salt, N, r, p) → 派生密钥
4. 使用 HUK 派生的 HMAC key 对派生密钥签名 → candidate hash
5. 恒定时间比较 candidate hash 与 handle 中存储的 hash
6. 如果匹配,生成 auth token(HMAC(challenge, SID))
↓ 返回结果
gatekeeperd ← 验证成功/失败 + auth token

LockSettingsService ← 更新失败计数器

DevicePolicyManager ← 检查是否触发 Wipe (失败次数超过阈值)

SyntheticPasswordManager ← 使用 auth token 解密 SP → 解锁 DEK

关键点:密码原文永远不会离开 TEE,Android 用户空间和 Linux 内核都无法接触到明文密码或派生密钥。

verifyCredential 详细代码路径

// frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java
private VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
int userId, long challenge) {
// 1. 从存储中读取已注册的 handle
CredentialHash storedHash = mStorage.readCredentialHash(userId);

// 2. 调用 gatekeeperd 验证
final GateKeeperResponse gateKeeperResponse = getGateKeeperService().verifyChallenge(
userId, challenge, storedHash.gatekeeperHandle, credential.getGatekeeperCredential());

// 3. 处理返回结果
VerifyCredentialResponse response = VerifyCredentialResponse.fromGateKeeperResponse(
gateKeeperResponse);

// 4. 如果成功,使用 auth token 解锁 Synthetic Password
if (response.getResponseCode() == RESPONSE_OK) {
// 通知 SyntheticPasswordManager 使用 auth token
mSpManager.unwrapPasswordBasedKey(response.getGatekeeperAuthToken());
// 重置失败计数器
mStrongAuthTracker.onSuccessfulAuthentication(userId);
} else {
// 更新失败计数和延迟
mStrongAuthTracker.onFailedAuthentication(userId);
}
return response;
}

Auth Token 机制

验证成功后,Gatekeeper TA 生成一个 auth token,格式为:

+------------------+
| version (1 byte) | -- token 版本
+------------------+
| challenge (8B) | -- 来自 verify 调用的 challenge(防重放)
+------------------+
| user_id (8B) | -- 用户 ID
+------------------+
| authenticator_id | -- SID(Secure Identifier)
| (8B) |
+------------------+
| authenticator_type| -- 0=Gatekeeper, 1=Fingerprint, 2=Face
| (4B) |
+------------------+
| timestamp (8B) | -- Unix 时间戳(毫秒)
+------------------+
| HMAC (32B) | -- HMAC-SHA256(shared_key, 以上所有字段)
+------------------+

Auth token 由 TimedAuthenticationToken 封装(Keystore 2.0 中):

system/security/keystore2/src/raw_device.cpp  — createAuthToken()
system/security/keystore2/keystore2_client.cpp — checkAuthToken()

Keystore 在使用 auth token 进行密钥授权时会验证:

  1. HMAC 完整性校验
  2. 时间戳是否在有效期(通常 5-10 分钟)内
  3. challenge 是否匹配(防重放)
  4. authenticator type 是否与密钥的需求匹配

时序攻击防护

Gatekeeper 在验证失败时会引入递增延迟:

// gatekeeperd 逻辑(简化)
// system/gatekeeper/gatekeeperd.cpp
if (verify_result == ERROR_RETRY_TIMEOUT) {
// 失败次数越多,等待时间越长(指数退避)
usleep(30 * 1000 * (1 << consecutive_failures));
}

这有效防止了暴力破解:失败 5 次后等待 30 × 2^4 = 480 秒 = 8 分钟;失败 10 次后等待 30 × 2^9 = 15360 秒 ≈ 4.3 小时。

此外,Gatekeeper TA 内部使用 恒定时间比较(constant-time comparison)来防止侧信道时序攻击:

// 恒定时间比较(简化)
int constant_time_compare(const uint8_t *a, const uint8_t *b, size_t len) {
uint8_t diff = 0;
for (size_t i = 0; i < len; i++) {
diff |= a[i] ^ b[i]; // XOR 每一位
}
return diff == 0; // 如果 diff 为 0 则相等
}

无论 hash 在第几个字节不匹配,循环总是遍历全部字节,比较操作耗时始终相同。

图案锁的存储

图案锁(Pattern Lock)的记录方式与 PIN/密码不同。图案被编码为一个字节序列,代表网格中的点连接顺序(3x3 网格,编号 1-9)。

3x3 网格的点编号:

1  2  3
4 5 6
7 8 9

例如图案 “L 形” 编码为 [1, 4, 7, 8, 9]。LockPatternUtils 中的编码逻辑:

// frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java
public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
if (pattern == null) return null;
final int patternSize = pattern.size();
byte[] res = new byte[patternSize];
for (int i = 0; i < patternSize; i++) {
LockPatternView.Cell cell = pattern.get(i);
res[i] = (byte)(cell.getRow() * 3 + cell.getColumn());
}
return res;
}

然后对该字节序列进行与密码相同的处理流程:

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 机制

  1. 用户设置锁屏密码 → Gatekeeper enroll → 生成 SID(Secure Identifier,64-bit 随机值)
  2. SID 被封装在 password handle 中,随 handle 一起存储
  3. Keystore 中的 auth-bound 密钥在生成时会绑定到当前 SID
  4. 每次验证密码成功后,Gatekeeper 输出 SID 和 auth token
  5. Keystore 用此 SID 验证 auth token 的合法性
  6. auth token 带有时间戳和 HMAC(用 Keystore 和 Gatekeeper 共享的密钥签名),用于授权 Keystore 操作
  7. 这个机制确保:只有知道锁屏密码的人才能使用设备上的加密密钥

Key Authorization 流程

Keystore 2.0 的密钥授权检查流程(简化):

应用请求使用密钥

Keystore 2.0 检查 key descriptor 中的 AuthorizationList

如果包含 KeyAuthorization.TAG_TRUSTED_USER_PRESENCE_REQUIRED:

调用 IAuthTokenManager.checkAuthorization(authToken, authTokenChallenge)

验证 auth token:
(1) HMAC 签名合法性
(2) 时间戳未过期(ChallengeExpirySeconds,默认 60 秒)
(3) SID 匹配
(4) AuthType 匹配(Password/Fingerprint/Face)

通过 → 执行密钥操作
拒绝 → 抛出 KeyPermanentlyInvalidatedException

AOSP 相关源码:

system/security/keystore2/src/authorization.cpp          — 授权检查实现
system/security/keystore2/src/auth_token_manager.cpp — auth token 管理
system/security/keystore/include/keystore/keymaster_enforcement.h — Keymaster enforcement

这就解释了为什么修改锁屏密码后,某些应用的加密数据会丢失——因为 Gatekeeper enroll 会生成新的 SID(除非使用 SyntheticPassword 的 re-enroll 路径),旧的 auth-bound 密钥再也无法通过 SID 匹配验证,从而永久失效。Android 9.0+ 的 SyntheticPassword 部分解决了这个问题(支持 re-enroll to new password 而不改变 DEK),但应用层 auth-bound 密钥仍可能受影响。

生物识别与 Gatekeeper 的集成

指纹和面部识别与 Gatekeeper 的协作机制:

  1. 用户解锁设备 → BiometricPrompt 调用 BiometricService.authenticate()
  2. Biometric HAL 验证指纹/面部成功后,调用 GateKeeper 的 verify 接口
  3. GateKeeper TA 生成 auth token(类型标识为 Fingerprint 或 Face)
  4. auth token 传递给 Keystore,允许使用 biometric-bound 密钥
  5. 注意:首次启动或 72 小时未解锁后,biometric auth token 失效,强制要求密码验证

AOSP 相关源码:

frameworks/base/services/core/java/com/android/server/biometrics/BiometricService.java
frameworks/base/services/core/java/com/android/server/biometrics/AuthSession.java
hardware/interfaces/biometrics/fingerprint/2.1/IBiometricsFingerprint.hal

安全评估与取证

获取密码的难度评估

场景 难度 说明
肩窥/涂抹痕迹 物理观察图案或 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.cpphardware/libhardware/include/hardware/gatekeeper.hframeworks/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.cppsystem/security/keystore2/src/authorization.cppsystem/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 算法论文与原始实现
打赏
  • 微信
  • 支付宝

评论