由硬件支持的密钥库
借助系统芯片 (SoC) 中提供的可信执行环境,Android 设备可以为 Android 操作系统、平台服务乃至第三方应用提供由硬件支持的强大安全服务。寻求 Android 专用扩展程序的开发者应访问 android.security.keystore。
在 Android 6.0 之前的版本中,Android 已有一个非常简单的由硬件支持的加密服务 API(由 0.2 和 0.3 版的 Keymaster 硬件抽象层 (HAL) 提供)。该密钥库能够提供数字签名和验证操作,以及不对称签名密钥对的生成和导入操作。该 API 在许多设备上都已实现,但有许多安全目标无法只通过一个签名 API 来轻松达成。Android 6.0 中的密钥库在该密钥库 API 的基础上进行了扩展,能够提供更广泛的功能。
在 Android 6.0 中,密钥库不仅增加了对称加密基元(AES 和 HMAC),还增加了针对由硬件支持的密钥的访问控制系统。访问控制在密钥生成期间指定,并会在密钥的整个生命周期内被强制执行。可以将密钥限定为仅在用户通过身份验证后才可使用,并且只能用于指定的目的或只有在具有指定的加密参数时才可使用。如需了解详情,请参阅授权标记和函数页面。
除了扩大加密基元的范围外,Android 6.0 中的 Keystore 还增加了以下内容:
- 一个使用控制方案,用于限制密钥的使用,并降低因滥用密钥而损害安全性的风险
- 一个访问控制方案,用于限定只有指定的用户和客户端能够使用相应密钥,并且只能在规定的时间范围内使用
在 Android 7.0 中,Keymaster 2 增加了对密钥认证和版本绑定的支持。密钥认证提供公钥证书,这些证书中包含密钥及其访问控制的详细描述,以使密钥存在于安全硬件中并使其配置可以远程验证。
版本绑定将密钥绑定至操作系统和补丁程序级别版本。这样可确保在旧版系统或 TEE 软件中发现漏洞的攻击者无法将设备回滚到易受攻击的版本,也无法使用在较新版本中创建的密钥。此外,在已经升级到更新的版本或补丁程序级别的设备上使用指定版本和补丁程序级别的密钥时,需要先升级该密钥才能使用,因为该密钥的旧版本已失效。当设备升级时,密钥会随着设备一起“升级”,但是将设备恢复到任何一个旧版本都会导致密钥无法使用。
在 Android 8.0 中,Keymaster 3 从旧式 C 结构硬件抽象层 (HAL) 转换到根据新硬件接口定义语言 (HIDL) 中的定义生成的 C++ HAL 接口。在此变更过程中,很多参数类型发生了变化,但这些类型和方法与旧的类型和 HAL 结构体方法一一对应。如需了解详情,请参阅函数页面。
除了此接口修订之外,Android 8.0 还扩展了 Keymaster 2 的认证功能,以支持 ID 认证。ID 认证提供了一种受限且可选的机制来严格认证硬件标识符,例如设备序列号、产品名称和手机 ID (IMEI/MEID)。为了实现此附加功能,需更改 ASN.1 认证架构以添加 ID 认证。Keymaster 实现需要通过某种安全方式来检索相关的数据项,还需要定义一种安全永久地停用该功能的机制。
在 Android 9 中,更新包括:
- 更新到 Keymaster 4
- 对嵌入式安全元件的支持
- 对安全密钥导入的支持
- 对 3DES 加密的支持
- 更改了版本绑定,以便 boot.img 和 system.img 分别设置版本以允许独立更新
术语库
下面简要介绍了各个 Keystore 组件及其关系。
AndroidKeystore 是供应用访问 Keystore 功能的 Android Framework API 和组件。它是作为标准 Java Cryptography Architecture API 的扩展程序实现的,包含在应用自己的进程空间中运行的 Java 代码。AndroidKeystore
通过将与密钥库行为有关的应用请求转发到密钥库守护程序来执行这些请求。
密钥库守护程序是 Android 系统中的一个守护程序,该进程通过 Binder API 提供对所有密钥库功能的访问权限。密钥库守护程序负责存储“密钥 Blob”。密钥 Blob 中包含已加密的实际密钥材料,因此密钥库可以存储这些材料,但无法使用或显示这些材料。
keymasterd 是一个 HIDL 服务器,可提供对 Keymaster TA 的访问权限。(此名称未进行标准化,仅用于说明概念。)
Keymaster TA(可信应用)是在安全环境(大多数情况为 ARM SoC 上的 TrustZone)中运行的软件。它可提供所有安全的密钥库操作,能够访问原始密钥材料,在密钥上验证所有访问控制条件,等等。
LockSettingsService 是负责用户身份验证(包括密码和指纹)的 Android 系统组件。它不是密钥库的一部分却与其相关,因为很多密钥库密钥操作都需要对用户进行身份验证。LockSettingsService
与 Gatekeeper TA 和 Fingerprint TA 进行交互以获取身份验证令牌,并将其提供给密钥库守护程序,这些令牌最终将由 Keymaster TA 应用使用。
Gatekeeper TA(可信应用)是在安全环境中运行的另一个组件,它负责验证用户密码并生成身份验证令牌(用于向 Keymaster TA 证明已在特定时间点完成对特定用户的身份验证)。
Fingerprint TA(可信应用)是在安全环境中运行的另一个组件,它负责验证用户指纹并生成身份验证令牌(用于向 Keymaster TA 证明已在特定时间点完成对特定用户的身份验证)。
架构
Android Keystore API 和底层 Keymaster HAL 提供了一套基本的但足以满足需求的加密基元,以便使用访问受控且由硬件支持的密钥实现相关协议。
Keymaster HAL 是由原始设备制造商 (OEM) 提供的动态加载库,Keystore 服务使用它来提供由硬件支持的加密服务。为了确保安全性,HAL 实现不会在用户空间乃至内核空间中执行任何敏感操作。敏感操作会被分配给通过某个内核接口连接的安全处理器。最终的架构如下所示:
图 1. 访问 Keymaster
在 Android 设备中,Keymaster HAL 的“客户端”包含多个层(例如,应用、框架、密钥库守护程序),但在本文档中可以将其忽略。这意味着,所介绍的 Keymaster HAL API 为底层 API,供平台内部组件使用,不面向应用开发者提供。Android 开发者网站对更高层 API 进行了介绍。
Keymaster HAL 的目的不是实现安全敏感型算法,而只是对发送到安全域的请求进行编排和解排。传输格式是由实现定义的。
与之前版本的兼容性
Keymaster 1 HAL 与之前发布的 HAL(例如,Keymaster 0.2 和 0.3)完全不兼容。为了在搭载 Android 5.0 及更低版本(采用旧版 Keymaster HAL)的设备上实现互用性,密钥库提供了一个可通过调用现有硬件库来实现 Keymaster 1 HAL 的适配器,但最终仍不能提供 Keymaster 1 HAL 中的全部功能。特别是,它仅支持 RSA 和 ECDSA 算法,而且所有密钥授权强制执行都由该适配器在非安全域中进行。
Keymaster 2 通过移除 get_supported_*
方法并允许 finish()
方法接受输入,进一步简化了 HAL 接口。在可一次性获得所有输入的情况下,这样可以减少到 TEE 的往返次数,并简化 AEAD 解密的实现过程。
在 Android 8.0 中,Keymaster 3 从旧式 C 结构 HAL 转换到根据新硬件接口定义语言 (HIDL) 中的定义生成的 C++ HAL 接口。通过对生成的 IKeymasterDevice
类进行子类化并实现纯虚方法创建了新式 HAL 实现。在此变更过程中,很多参数类型发生了变化,但这些类型和方法与旧的类型和 HAL 结构体方法一一对应。
HIDL 概览
硬件接口定义语言 (HIDL) 提供了一种独立于实现语言的机制来指定硬件接口。HIDL 工具目前支持生成 C++ 和 Java 接口。大多数可信执行环境 (TEE) 实现人员应该都会发现 C++ 工具更加方便,因此本文档仅讨论 C++ 表示法。
HIDL 接口由一组方法组成,表示如下:
methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);
有很多不同的预定义类型,而 HAL 可以定义新的枚举和结构类型。如需详细了解 HIDL,请参阅“参考”部分。
下面显示了 Keymaster 3 IKeymasterDevice.hal
中的一个示例方法:
generateKey(vec<KeyParameter> keyParams) generates(ErrorCode error, vec<uint8_t> keyBlob, KeyCharacteristics keyCharacteristics);
这相当于 keymaster2 HAL 中的以下方法:
keymaster_error_t (*generate_key)( const struct keymaster2_device* dev, const keymaster_key_param_set_t* params, keymaster_key_blob_t* key_blob, keymaster_key_characteristics_t* characteristics);
在 HIDL 版本中,由于 dev
参数采用隐式形式,因此已被移除。params
参数不再是包含引用了一组 key_parameter_t
对象的指针的结构体,而是包含 KeyParameter
对象的 vec
(矢量)。返回值在“generates
”子句中列出,其中包含密钥 Blob 的 uint8_t
值的矢量。
由 HIDL 编译器生成的 C++ 虚拟方法为:
Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams, generateKey_cb _hidl_cb) override;
其中,generate_cb
是一个函数指针,定义如下:
std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob, const KeyCharacteristics& keyCharacteristics)>
也就是说,generate_cb
是一个将接受 generate 子句中列出的返回值的函数。HAL 实现类会覆盖此 generateKey
方法并调用 generate_cb
函数指针,以将操作结果返回给调用方。请注意,该函数指针调用是同步的。调用方调用 generateKey
,同时 generateKey
调用所提供的函数指针,该指针在完成执行操作后将控件返回到 generateKey
实现,而后该实现又返回到调用方。
如需查看详细示例,请参阅 hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp
中的默认实现。该默认实现可向后兼容采用旧式 keymaster0、keymaster1 或 keymaster2 HAL 的设备。
功能
本页中包含与 Android 6.0 及更高版本中的密钥库加密功能相关的信息。
注意:标记和功能以 Keymaster 3 样式进行编写。如需了解详情,请参阅 HIDL 概览。
加密基元
Keystore 能够提供以下类别的操作:
- 生成密钥
- 导入和导出不对称密钥(无密钥封装)
- 导入原始对称密钥(无密钥封装)
- 使用适当的填充模式进行不对称加密和解密
- 使用摘要和适当的填充模式进行不对称签名和验证
- 以适当模式(包括 AEAD 模式)进行对称加密和解密
- 生成和验证对称消息验证码
生成或导入密钥时,系统会指定协议元素(例如,用途、模式和填充,以及访问控制限制),这些元素会永久绑定到相应密钥,以确保无法以任何其他方式使用相应密钥。
除了上面列出的操作外,Keymaster 实现还提供另外一项服务,即随机数生成服务,但该服务并不作为 API 进行提供。该服务仅供在内部使用,用于生成密钥、初始化矢量 (IV)、随机填充,以及其他需要具有随机性的安全协议元素。
必要基元
所有 Keymaster 实现都提供:
- RSA
- 支持 2048 位、3072 位和 4096 位密钥
- 支持公开指数 F4 (2^16+1)
- RSA 签名所需的填充模式:
- RSASSA-PSS (
PaddingMode::RSA_PSS
) - RSASSA-PKCS1-v1_5 (
PaddingMode::RSA_PKCS1_1_5_SIGN
)
- RSASSA-PSS (
- RSA 签名所需的摘要模式:
- SHA-256
- RSA 加密/解密所需的填充模式:
- 无填充
- RSAES-OAEP (
PaddingMode::RSA_OAEP
) - RSAES-PKCS1-v1_5 (
PaddingMode::RSA_PKCS1_1_5_ENCRYPT
)
- ECDSA
- 支持 224 位、256 位、384 位和 521 位密钥,分别使用 NIST P-224、P-256、P-384 和 P-521 曲线
- ECDSA 所需的摘要模式:
- 无摘要(已弃用,将于日后移除)
- SHA-256
- AES
- 支持 128 位和 256 位密钥
- CBC、CTR、ECB 和 GCM。GCM 实现不允许使用少于 96 位的标记,也不允许使用 96 位以外的随机数长度。
- CBC 和 ECB 模式支持填充模式
PaddingMode::NONE
和PaddingMode::PKCS7
。采用“无填充”时,如果输入的不是分块大小的倍数,CBC 或 ECB 模式的加密会失败。
- HMAC SHA-256,其中任意密钥均不得短于 32 个字节。
对于 Keymaster 实现,强烈建议提供 SHA1,以及 SHA2 系列的其他成员(SHA-224、SHA384 和 SHA512)。如果硬件 Keymaster 实现未提供这些内容,则 Keystore 会在软件中提供它们。
此外,为了实现与其他系统的互用性,还建议提供以下基元:
- 适用于 RSA 的较小密钥大小
- 适用于 RSA 的任意公开指数
密钥访问控制
如果攻击者可以随意使用在任何情况下都无法从设备获取的基于硬件的密钥,那么此类密钥将无法提供太多的安全性(尽管它们比可被窃取的密钥更安全)。因此,密钥库强制执行访问权限控制至关重要。
访问权限控制指的是由“标记/值”对组成的“授权列表”。授权标记是 32 位整数,其值有多种类型。有些标记可以重复使用,以指定多个值。某个标记是否可重复使用是在关于该标记的文档中指定的。密钥创建好后,调用程序会指定一个授权列表。Keymaster 实现使用的底层 Keystore 会修改该列表,以指定一些额外的信息(例如,密钥是否有防回滚保护),并且会返回一个“最终”授权列表(编码到返回的密钥 Blob 中)。如果最终授权列表被修改了,那么任何尝试使用相应密钥进行任何加密操作的行为都会失败。
对于 Keymaster 2 及更早版本,枚举 keymaster_authorization_tag_t
中定义了一组可能的标记,这组标记永远保持不变(不过可进行扩展)。这些标记的名称带有 KM_TAG
前缀。标记 ID 的前四位用于指明类型。
Keymaster 3 将 KM_TAG
前缀改为 Tag::
。
可能的类型包括:
ENUM
:很多标记的值都是在枚举中定义的。例如,TAG::PURPOSE
的可能值是在枚举 keymaster_purpose_t
中定义的。
ENUM_REP
:与 ENUM
相同,不过此标记可在授权列表中重复使用。重复使用此标记表明有多个已获授权的值。例如,加密密钥可能具有 KeyPurpose::ENCRYPT
和 KeyPurpose::DECRYPT
。
UINT
:32 位未签名整数。示例:TAG::KEY_SIZE
UINT_REP
:与 UINT
相同,不过此标记可在授权列表中重复使用。重复使用此标记表明有多个已获授权的值。
ULONG
:64 位未签名整数。示例:TAG::RSA_PUBLIC_EXPONENT
ULONG_REP
:与 ULONG
相同,不过此标记可在授权列表中重复使用。重复使用此标记表明有多个已获授权的值。
DATE
:日期/时间值,以距 1970 年 1 月 1 日的毫秒数表示。示例:TAG::PRIVKEY_EXPIRE_DATETIME
BOOL
:true 或 false。对于 BOOL
类型的标记,如果不存在则被视为“false”,如果存在则被视为“true”。示例:TAG::ROLLBACK_RESISTANT
BIGNUM
:任意长度的整数,以字节数数组表示(采用大端字节序)。示例:TAG::RSA_PUBLIC_EXPONENT
BYTES
:一系列字节数。示例:TAG::ROOT_OF_TRUST
硬件与软件强制执行
并非所有安全硬件实现都包含相同的功能。为了支持多种方法,Keymaster 会对安全域和非安全域访问控制强制执行(分别称为硬件强制执行和软件强制执行)加以区分。
所有实现:
- 强制执行所有授权完全匹配(不是强制执行所有授权)。密钥 Blob 中的授权列表与密钥生成期间返回的授权完全匹配(包括顺序)。如有任何不匹配,都会导致进行错误诊断。
- 声明语义值会被强制执行的授权。
用于声明由硬件强制执行的授权的 API 机制位于 keymaster_key_characteristics_t
结构中。它将授权列表划分成两个子列表:hw_enforced
和 sw_enforced
。安全硬件负责根据它可以强制执行的内容在每个子列表中放入适当的值。
此外,Keystore 会实现基于软件强制执行所有授权,无论它们是否由安全硬件强制执行。
让我们以一个不支持密钥过期日期且基于 TrustZone 的实现为例。实现仍可能会创建一个具有过期日期的密钥。该密钥的授权列表将包含具有过期日期的 TAG::ORIGINATION_EXPIRE_DATETIME
标记。向密钥库发出的密钥特性请求将会在 sw_enforced
列表中找到此标记,并且安全硬件不会强制执行过期日期要求。不过,如果尝试在过期日期之后使用该密钥,则会被密钥库拒绝。
如果设备随后进行了升级,采用了不支持过期日期的安全硬件,那么密钥特性请求将会在 hw_enforced
列表中找到 TAG::ORIGINATION_EXPIRE_DATETIME
,并且即使以某种方式破坏或规避密钥库,尝试在过期日期之后使用相应密钥也会失败。
如需详细了解如何确定密钥是否受硬件支持,请参阅密钥认证。
(由于感觉看英文不那么顺,就直接看网页的中文版本,结果上面一段看得有点绕,干脆直接看英文):
If the device is then upgraded with secure hardware that does support expiration, then a request for key characteristics will find
TAG::ORIGINATION_EXPIRE_DATETIME
in thehw_enforced
list, and attempts to use the key after expiration will fail even if the keystore is somehow subverted or bypassed.
好家伙,直接把意思翻译反了,英文说支持过期日期,翻译的内容却是:不支持过期日期。看来直接看英文还是保险点,或者结合起来看。