摘要
本文是对RFC 9807所描述的增强型口令认证密钥交换协议OPAQUE的学习与梳理,包括:1)协议整体执行流程、2)注册方案和在线认证方案、3)关键模块OPRF与3DH。文章保留了原标准的章节对应关系,方便对照RFC原文阅读。
关键词:口令认证、OPAQUE、OPRF、aPAKE、3DH、密钥交换
本文导读
这是啥——这是RFC 9807 OPAQUE协议的配套学习笔记,先总览、再分模块、最后看流程,适合对照原文阅读,也适合想快速搞懂原理的读者通读一遍。
哪个读——密码协议研究者、安全开发工程师,以及正在学习PAKE、OPRF的童鞋。阅读前建议先了解OPRF(不经意伪随机函数,可参考RFC 9497)和DH密钥交换。
啷个读——建议先看第2章的架构总览,建立两个阶段和三条消息的整体印象;再按兴趣深入第3、4章的两个关键模块;第6、7章是完整的注册和登录流程,完整详情可以随时配套阅读RFC原文。
结算——OPAQUE用OPRF把口令隐蔽起来,让服务器在看不到口令的情况下,仍能和客户端完成双向认证并协商出会话密钥;即使服务器数据库泄露,攻击者也无法用彩虹表等手段批量破解口令。
7. 在线认证密钥交换流程(对应第6章)
7.1 在线认证全流程概述
在线认证通过四个函数、三条消息完成:
- 客户端执行GenerateKE1创建KE1并发给服务器;
- 服务器执行GenerateKE2创建KE2发回;
- 客户端执行GenerateKE3创建KE3发出,同时得到session_key和export_key;
- 服务器执行ServerFinish核验KE3,得到session_key。
图7-1在线认证阶段交互图(复用图2-4)
7.2 在线认证的状态和消息(对应6.1节)
7.2.1 ClientState(客户端状态)
struct {
uint8 password[*]; // 客户端口令
uint8 blind[Nok]; // OPRF盲化因子
ClientAkeState clientakestate;
} ClientState;
7.2.2 ServerState(服务器状态)
struct {
ServerAkeState state;
} ServerState;
7.2.3 KE1、KE2、KE3消息
这三条消息的具体字段定义见第4.2节,这里不再重复。
7.3 在线认证执行函数
7.3.1 GenerateKE1(对应6.2.1节)
ke1 = GenerateKE1(password)
- 功能:客户端启动登录、生成KE1。
- 关键步骤:调用CreateCredentialRequest生成OPRF请求和盲化因子,把口令和盲化因子存进ClientState;再调用AuthClientStart生成KE1。
7.3.2 GenerateKE2(对应6.2.2节)
ke2 = GenerateKE2(server_identity, server_private_key, server_public_key, record, credential_identifier, oprf_seed, ke1, client_identity)
- 功能:服务器生成KE2。
- 关键步骤:
- 调用CreateCredentialResponse生成OPRF响应;
- 构造明文凭证;
- 调用AuthServerRespond生成AKE响应;
- 把两部分合成KE2。
7.3.3 GenerateKE3(对应6.2.3节)
(ke3, session_key, export_key) = GenerateKE3(client_identity, server_identity, ke2)
- 功能:客户端完成AKE、生成KE3。
- 关键步骤:调用RecoverCredentials恢复客户端私钥和凭证;再调用AuthClientFinalize校验服务器、生成KE3和会话密钥。
- 由于OPRF是两消息协议,KE3里不再包含OPRF元素。
7.3.4 ServerFinish(对应6.2.4节)
session_key = ServerFinish(ke3)
- 功能:服务器完成AKE。
- 关键步骤:直接调用AuthServerFinalize核验KE3,在使用session_key之前必须先确认客户端认证材料有效。
7.4 OPRF和凭证相关函数(对应6.3节)
7.4.1 CredentialRequest(凭证请求)
struct {
uint8 blinded_message[Noe];
} CredentialRequest;
7.4.2 CredentialResponse(凭证响应)
struct {
uint8 evaluated_message[Noe];
uint8 masking_nonce[Nn];
uint8 masked_response[Npk + Nn + Nm];
} CredentialResponse;
7.4.3 CreateCredentialRequest(生成凭证请求)
(request, blind) = CreateCredentialRequest(password)
- 功能:执行OPRF盲化并生成凭证请求,逻辑与注册阶段的CreateRegistrationRequest一致。
7.4.4 CreateCredentialResponse(生成凭证响应)
response = CreateCredentialResponse(request, server_public_key, record,
credential_identifier, oprf_seed)
- 功能:执行OPRF盲化求值,并把信封加密后生成凭证响应。
- 关键步骤:
- 派生OPRF私钥、执行BlindEvaluate;
- 生成随机masking_nonce,用注册记录里的掩码密钥扩展出一段与待加密内容等长的填充:
credential_response_pad = Expand(record.masking_key, masking_nonce || "CredentialResponsePad", Npk + Nn + Nm)
masked_response = xor(credential_response_pad, server_public_key || record.envelope)
这样信封和服务器公钥在传输过程中是被一次性掩码保护的。
这里要处理两种情形,关系到能否防止用户枚举:
- 用户记录真实存在:正常调用CreateCredentialResponse即可。
- 用户从未注册(可能是枚举攻击):服务器若要防枚举,仍必须返回看起来合法的响应,用伪造记录来调用。伪造记录包括:随机的client_public_key、随机的masking_key、全零的envelope。这样从攻击者的响应里无法判断用户是否存在。
7.4.5 RecoverCredentials(恢复凭证)
(client_private_key, cleartext_credentials, export_key) = RecoverCredentials(password, blind, response, server_identity, client_identity)
- 功能:客户端执行OPRF去盲化并恢复私钥等凭证。
- 关键步骤:
- 执行Finalize得到OPRF输出,经KSF拉伸、Extract得到randomized_password;
- 从rpwd派生掩码密钥,扩展出填充,异或解密出信封和服务器公钥:
masking_key = Expand(randomized_password, "MaskingKey", Nh)
credential_response_pad = Expand(masking_key, response.masking_nonce || "CredentialResponsePad", Npk + Nn + Nm)
server_public_key || envelope = xor(credential_response_pad, response.masked_response)
- 调用Recover恢复客户端私钥、明文凭证和导出密钥。