目录
  1. 1. 前言
  2. 2. 密码存储的演进
  3. 3. Gatekeeper 架构概述
  4. 4. 密码文件格式详解
    1. 4.1. gatekeeper.password.key / gatekeeper.pattern.key
    2. 4.2. 老版本格式(兼容性参考)
  5. 5. scrypt 密钥派生函数
  6. 6. Gatekeeper 验证流程
    1. 6.1. 时序攻击防护
  7. 7. 图案锁的存储
  8. 8. 与 Hardware-Backed Keystore 的集成
  9. 9. 面试常考问题
  10. 10. 参考
【逆向安全技术-基础篇】锁屏密码加密算法分析

前言

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

Hardware-Backed (HMAC key 由硬件保护)
  • gatekeeperd (/system/bin/gatekeeperd):运行在 Android 用户空间的守护进程,负责接收 LockSettingsService 的验证请求。
  • Gatekeeper HALhardware/libhardware/include/hardware/gatekeeper.h):定义 HAL 接口(enroll / verify)。
  • Keymaster HAL:与 Gatekeeper 协作,提供硬件支持的密钥操作。

密码文件格式详解

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 并不是简单的 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,
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):CPU/内存成本因子,必须为 2 的幂。值越大越安全但越慢。
  • r (通常为 8):块大小因子。
  • p (通常为 1):并行度因子。
  • salt:随机盐值,防止彩虹表攻击。

Android 中 scrypt 在 Gatekeeper 的初始实现(Android 5.0/5.1)中用于软件层密码派生;从 Android 6.0 开始,密码派生被移入 TEE,scrypt 参数由 TA 决定,宿主机无法直接干预。

Gatekeeper 验证流程

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

用户输入密码

LockSettingsService.verifyPassword()
↓ Binder 调用
gatekeeperd (native daemon)
↓ 读取 /data/system/gatekeeper.password.key
提取 handle(含 salt + 加密参数)
↓ ioctl / HAL 调用
Gatekeeper TA (TEE 中运行)

1. 从 handle 中恢复 salt 和 scrypt 参数
2. 对输入的密码执行 scrypt(password, salt, N, r, p) → 派生密钥
3. 使用硬件 HMAC key 对派生密钥签名 → candidate hash
4. 与 handle 中存储的 hash 比较
↓ 返回结果
gatekeeperd ← 验证成功/失败

LockSettingsService ← 更新失败计数器

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

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

时序攻击防护

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

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

此外,Gatekeeper 会使用 恒定时间比较 来防止侧信道时序攻击,即无论 hash 在第几个字节不匹配,比较操作耗时始终相同。

图案锁的存储

图案锁(Pattern Lock)的记录方式与 PIN/密码不同。图案被编码为一个字节序列,代表网格中的点连接顺序(3x3 网格,编号 1-9)。例如图案 “L 形” 编码为 [1, 4, 7, 8, 9],然后对该字节序列进行与密码相同的处理流程:

pattern = [1, 4, 7, 8, 9]
pattern_bytes = 序列化为字节串
scrypt(pattern_bytes, salt, N, r, p) → 经过 Gatekeeper 加密签名

与 Hardware-Backed Keystore 的集成

Android Keystore 2.0(Android 12+)将 Gatekeeper 与 Keystore 更紧密地结合:

  1. 用户设置锁屏密码 → Gatekeeper enroll → 生成 SID(Secure Identifier)
  2. Keystore 中的 auth-bound 密钥在生成时会绑定到当前 SID
  3. 每次验证密码成功后,Gatekeeper 输出有效的 SID,Keystore 用此 SID 生成 auth token
  4. auth token 带有时间戳和 MAC,用于授权 Keystore 操作
  5. 这个机制确保:只有知道锁屏密码的人才能使用设备上的加密密钥

这就解释了为什么修改锁屏密码后,某些应用的加密数据会丢失——因为 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 算法论文与原始实现
打赏
  • 微信
  • 支付宝

评论