### 端到端加密 Matrix 可选支持端到端加密,允许创建房间,其对话内容无法被任何参与的主服务器解密或截获。 #### 密钥分发 Matrix 中的加密与身份验证基于公钥密码学。Matrix 协议提供了一个基础的公钥交换机制,但需要通过带外渠道在用户之间交换指纹,以建立信任网络。 ##### 概述 1)Bob 发布其设备的公钥和支持的算法。这可能包括长期身份密钥和/或一次性密钥。 ``` +----------+ +--------------+ | Bob 的主服务器 | | Bob 的设备 | +----------+ +--------------+ | | |<=============| /keys/upload ``` 2)Alice 请求 Bob 的公用身份密钥和支持的算法。 ``` +----------------+ +------------+ +----------+ | Alice 的设备 | | Alice 的主服务器 | | Bob 的主服务器 | +----------------+ +------------+ +----------+ | | | |=================>|==============>| /keys/query ``` 3)Alice 选择一种算法并领取所需的一次性密钥。 ``` +----------------+ +------------+ +----------+ | Alice 的设备 | | Alice 的主服务器 | | Bob 的主服务器 | +----------------+ +------------+ +----------+ | | | |=================>|==============>| /keys/claim ``` ##### 密钥算法 不同的密钥算法用于不同的用途。每种密钥算法通过名称标识,并有特定的表示方式。 名称 `ed25519` 对应于 [Ed25519](http://ed25519.cr.yp.to/) 签名算法。密钥为一个 32 字节的 Ed25519 公钥,使用 [unpadded Base64](/appendices/#unpadded-base64) 编码。例如: "SogYyrkTldLz0BXP+GYWs0qaYacUI0RleEqNT8J3riQ" 名称 `curve25519` 对应于 [Curve25519](https://cr.yp.to/ecdh.html) ECDH 算法。密钥为一个 32 字节的 Curve25519 公钥,使用 [unpadded Base64](/appendices/#unpadded-base64) 编码。例如: "JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y" 名称 `signed_curve25519` 也对应于 Curve25519 ECDH 算法,但该密钥经过签名,可以进行身份认证。使用此算法的密钥以包含以下属性的对象表示: `KeyObject` | 参数 | 类型 | 描述 | |------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------| | key | string | **必选。** 未填充的 Base64 编码的 32 字节 Curve25519 公钥。 | | signatures | Signatures | **必选。** 密钥对象的签名。签名过程详见 [Signing JSON](/appendices/#signing-json)。 | | fallback | boolean | 标记此密钥是否为 [备用密钥](#one-time-and-fallback-keys)。默认为 `false`。 | 示例: ```json { "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", "signatures": { "@user:example.com": { "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" } } } ``` `ed25519` 和 `curve25519` 密钥用于 [设备密钥](#device-keys)。此外,`ed25519` 密钥也用于 [跨签名密钥](#cross-signing)。 `signed_curve25519` 密钥用于 [一次性及备用密钥](#one-time-and-fallback-keys)。 ##### 设备密钥 每台设备应拥有一个 Ed25519 签名密钥。此密钥应通过设备上的加密安全源生成,且其私钥部分绝不可导出。其他客户端通过该密钥作为设备指纹,并由该密钥签署设备的其他密钥。 设备通常还需要生成若干附加密钥,具体取决于使用的消息算法。 在 Olm 1.0 版本中,每台设备还需有一个 Curve25519 身份密钥。 ##### 一次性与备用密钥 除了设备密钥(属于长期密钥),某些加密算法还要求设备拥有多个一次性密钥,这些密钥仅使用一次,用后即弃。在 Olm 1.0 版本中,设备使用由设备 Ed25519 密钥签名的 `signed_curve25519` 一次性密钥。 设备会生成一次性密钥上传至服务器。其他用户之后会通过 [claim](#post_matrixclientv3keysclaim) 领取。服务器必须确保每个一次性密钥只被领取一次:主服务器在将该一次性密钥分配给其他用户后即应删除。 {{% added-in v="1.2" %}} 备用密钥与一次性密钥类似,但使用后不会被消耗。如果上传了备用密钥,当设备用尽一次性密钥、用户请求密钥时,服务器会返回备用密钥。备用密钥一旦被使用,应尽快用新密钥替换。 {{% boxes/warning %}} 备用密钥用于在设备离线或无法上传新密钥时防止一次性密钥耗尽。但使用备用密钥建立的会话可能易受重放攻击。 {{% /boxes/warning %}} 设备将通过 [`/sync`](#e2e-extensions-to-sync) 得知尚可被领取的一次性密钥数量,以及备用密钥是否已被使用。这样设备即可保障在线期间有充足一次性密钥,并在备用密钥被用后及时更换。 ##### 密钥上传 设备通过 [`/keys/upload`](/client-server-api/#post_matrixclientv3keysupload) API 将身份密钥的公钥部分以签名 JSON 对象形式上传至主服务器。JSON 对象必须包含设备 Ed25519 密钥的公钥部分,且必须由该密钥签名,详见 [Signing JSON](/appendices/#signing-json)。 一次性密钥和备用密钥也通过 [`/keys/upload`](/client-server-api/#post_matrixclientv3keysupload) API 上传。如需上传新的一次性或备用密钥则再上传。备用密钥(格式为签名 JSON 对象的密钥算法)应包含名为 `fallback` 且值为 `true` 的属性。 设备必须保存他们上传的每个密钥的私钥部分。当收到使用该密钥加密的信息后,可以删除一次性密钥的私钥部分。但有可能主服务器发出的某个一次性密钥从未被用过,因此生成密钥的设备永远也不会知道可以删除该密钥。因此,设备最终可能需要保管过多私钥。若存储私钥过多,设备可以从最早的密钥开始丢弃。 {{% boxes/warning %}} 客户端不应无限期保存备用密钥的私钥,以免攻击者能解密此前用该备用密钥加密的消息。 客户端最多应该只保存 2 个备用密钥的私钥:当前未用的备用密钥及其前一个。一旦客户端确信所有用旧备用密钥加密的消息都已收到(如自第一条消息后经过一小时),应删除该备用密钥。 {{% /boxes/warning %}} ##### 跟踪用户的设备列表 在 Alice 向 Bob 发送加密消息之前,需要获得 Bob 每台设备及相关身份密钥的列表,以建立这些设备的加密会话。此列表可通过 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery) 并在 `device_keys` 参数中传入 Bob 用户 ID 获得。 Bob 可能会不时添加新设备,Alice 需要及时获知并在今后的加密消息中加上新的设备。最简单做法是每发一次消息都执行一次 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery),但用户数和设备数可能庞大,如此会极度低效。 因此,通常每个客户端会维护一些用户(实践中通常是与之共享加密房间的用户)的设备列表。该列表必须在客户端应用程序多次启动间持久化存储(以保留设备验证数据并在 Bob 突然添加新设备时警告 Alice)。 Alice 的客户端可按以下流程维护 Bob 的设备列表: 1. 首先设置标记,记录现在正在跟踪 Bob 的设备列表,并有单独标记表示本地 Bob 设备列表已过期。两者都必须保存在客户端重启后依然存在的存储中。 2. 向 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery) 发送请求,`device_keys` 参数传入 Bob 用户 ID。请求完成后,将获取到的设备列表持久化保存,并清除“已过期”标记。 3. 正常处理 [`/sync`](/client-server-api/#get_matrixclientv3sync) 响应时,解析 [`device_lists`](#e2e-extensions-to-sync) 字段的 `changed` 属性。如果在跟踪于列出的用户之一的设备列表,则将该用户的设备列表标记为过期,并重发 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery) 请求。 4. 定期将 [`/sync`](/client-server-api/#get_matrixclientv3sync) 结果中的 `next_batch` 字段保存持久化。如果 Alice 后续重启客户端,可通过 [`/keys/changes`](/client-server-api/#get_matrixclientv3keyschanges) 并将记录的 `next_batch` 字段作为 `from` 参数查询在离线期间哪些用户的设备列表已变更。如果在跟踪这些用户的设备列表,则将其标记为过期。将此列表与之前已标记过期的列表并集,然后对这些用户重新执行 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery)。 {{% boxes/warning %}} Bob 可能在 Alice 的 `/keys/query` 请求仍在进行时更新设备。Alice 的客户端可能会在第一次请求未结束时,在 `/sync` 响应的 `device_lists` 字段中看到 Bob 的用户 ID,并发起第二次 `/keys/query` 请求。这可能导致两类相关问题。 第一个问题:当第一次请求响应时,客户端会清除记录的 Bob 设备已过期标记。如果第二次请求失败或在完成前客户端关闭,可能导致 Alice 继续使用已过期的 Bob 设备列表。 第二个问题:在某些条件下,第二次请求可能早于第一次请求完成。此时第一次请求的结果会覆盖第二次的结果。 客户端必须防范上述情况。例如,可以确保每个用户至多只有一个 `/keys/query` 请求在运行,对于新的请求排队等待第一个完成。或立即发起新请求,但确保忽略第一次请求的结果(例如通过取消第一次请求)。 {{% /boxes/warning %}} {{% boxes/note %}} 当 Bob 和 Alice 共享房间并跟踪 Alice 设备时,Alice 离开房间后新增设备,Bob 不会知晓。当他们再次共享房间时,Bob 就有一个过时的 Alice 设备列表。为了解决该问题,Bob 的主服务器会把 Alice 的用户 ID 加入 `device_lists` 字段的 `changed` 属性中,因此 Bob 会按常规流程更新 Alice 的设备列表。另外,Bob 也可通过检查 `device_lists` 字段的 `left` 属性获知双方已不再共享任何房间,并相应地将 Alice 从跟踪列表移除。 {{% /boxes/note %}} ##### 发送加密附件 当房间启用加密时,文件应在上传到主服务器前进行加密。 操作方式为,客户端生成一个只用一次的 256 位 AES 密钥并使用 AES-CTR 模式加密该文件。计数器(Counter)应为 64 位,从 0 开始,且以随机 64 位初始化向量(IV)为前缀,两者一起组成 128 位唯一计数器块。 {{% boxes/warning %}} IV 绝不可与同一密钥重复使用。这意味着如需加密同一消息中的多个文件(如图片和缩略图),绝不可共用密钥及 IV。 {{% /boxes/warning %}} 加密后,文件即可上传到主服务器。必须在房间事件中包含密钥和 IV 以及上传得到的 `mxc://`,以便接收方可以解密。由于承载这些密钥和 IV 的事件会用 Megolm 加密,服务器无法解密文件。 还必须包含密文的哈希值,以防主服务器篡改文件内容。 客户端应以加密后的 `m.room.message` 事件形式发送数据,`msgtype` 可用 `m.file`,或其他对应文件类型的 msgtype。密钥采用 [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) 格式,带 [W3C 扩展](https://w3c.github.io/webcrypto/#iana-section-jwk)。 ###### 对 `m.room.message` 的扩展 本模块为引用文件的 `m.room.message` 消息类型添加了 `file` 和 `thumbnail_file` 属性,类型为 `EncryptedFile`,用于替代 `url` 和 `thumbnail_url` 属性,如 [m.file](#mfile) 和 [m.image](#mimage) 等。 `EncryptedFile` | 参数 | 类型 | 描述 | |-----------|----------------|----------------------------------------------------------------------------------| | url | string | **必选。** 文件的 URL。 | | key | JWK | **必选。** 一个 [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) 对象。 | | iv | string | **必选。** AES-CTR 使用的 128 位唯一计数块,未填充 base64 编码。 | | hashes | {string: string} | **必选。** 算法名称与密文哈希的映射,未填充 base64 编码。客户端应至少支持 `sha256` 哈希。 | | v | string | **必选。** 加密附件协议版本,必须为 `v2`。 | `JWK` | 参数 | 类型 | 描述 | |--------|----------|----------------------------------------------------------------------------------------| | kty | string | **必选。** 密钥类型,必须为 `oct`。 | | key_ops| [string] | **必选。** 密钥操作。至少包含 `encrypt` 和 `decrypt`。 | | alg | string | **必选。** 算法,必须为 `A256CTR`。 | | k | string | **必选。** 密钥,以 urlsafe 未填充 base64 编码。 | | ext | boolean | **必选。** 可提取性。必须为 `true`。此为 [W3C 扩展](https://w3c.github.io/webcrypto/#iana-section-jwk)。 | 示例: ```json { "content": { "body": "something-important.jpg", "file": { "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", "v": "v2", "key": { "alg": "A256CTR", "ext": true, "k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0", "key_ops": ["encrypt","decrypt"], "kty": "oct" }, "iv": "w+sE15fzSc0AAAAAAAAAAA", "hashes": { "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" } }, "info": { "mimetype": "image/jpeg", "h": 1536, "size": 422018, "thumbnail_file": { "hashes": { "sha256": "/NogKqW5bz/m8xHgFiH5haFGjCNVmUIPLzfvOhHdrxY" }, "iv": "U+k7PfwLr6UAAAAAAAAAAA", "key": { "alg": "A256CTR", "ext": true, "k": "RMyd6zhlbifsACM1DXkCbioZ2u0SywGljTH8JmGcylg", "key_ops": ["encrypt", "decrypt"], "kty": "oct" }, "url": "mxc://example.org/pmVJxyxGlmxHposwVSlOaEOv", "v": "v2" }, "thumbnail_info": { "h": 768, "mimetype": "image/jpeg", "size": 211009, "w": 432 }, "w": 864 }, "msgtype": "m.image" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", "type": "m.room.message", "unsigned": { "age": 1234 } } ``` #### 设备验证 在 Alice 向 Bob 发送加密数据、或信任从 Bob 收到的数据之前,或许想先验证自己真正与 Bob 本人通信,而非与中间人。此验证过程需要带外渠道:仅依赖 Matrix 自身无法做到完全安全验证(否则就只能信任主服务器管理员了)。 在 Matrix 中,验证流程通常是 Alice 与 Bob 线下见面或通过其他可靠方式交流,采用下文中定义的任一种互动验证方式校验 Bob 的设备。Alice 和 Bob 也可以当面逐字读出各自的未填充 base64 编码 Ed25519 公钥(如 `/keys/query` 返回值)。 设备验证可能有多种结论。例如: - Alice 可以“接受”设备,表明她确信设备属于 Bob,今后可以为该设备加密敏感内容,并确认接收到的消息确实由该设备发出。 - Alice 可以“拒绝”设备,如果她知道或怀疑该设备非 Bob 控制(如也不信任 Bob),则不会发送敏感内容、不再信任该设备发来的消息。 - Alice 可以跳过设备验证流程。这种情况下虽然无法确信设备属于 Bob,但也没有证据怀疑,且加密协议依然能够防止被动窃听。 {{% boxes/note %}} 一旦签名密钥通过验证,就由加密协议去验证具体消息是否真由持有该 Ed25519 私钥的设备发出,或仅允许该设备解密。Olm 协议的具体文档见 。 {{% /boxes/note %}} ##### 密钥验证框架 人工比对 Ed25519 密钥并不友好,易出错。为降低错误概率、提升用户体验,本规范支持一些验证方式,并允许通过用户设备间互发消息辅助验证。这些方法共用一套验证协商框架。 验证消息可以发在由双方共享的房间(应为 [定向消息](#direct-messaging)),也可通过 [to-device](#send-to-device-messaging) 消息点对点直发两个设备。两种场景下报文结构类似,细微差别详见下文。验证不同用户应用房间消息,同一用户多个设备验证则用 to-device 消息。 一个密钥验证会话以首条消息中的 ID 标识。房间消息用初始 message 的 event ID,to-device 消息用首条消息的 `transaction_id` 字段,其余同会话消息继承。 一般来说,验证流程如下: - Alice 通过发送密钥验证请求事件,向 Bob 发起验证请求。如果在房间,事件类型为 [`m.room.message` 且 `msgtype: m.key.verification.request`](#mroommessagemkeyverificationrequest),如用 to-device 则类型为 [`m.key.verification.request`](#mkeyverificationrequest)。事件指明 Alice 客户端支持的验证方式。(注意“Alice”与“Bob”在自验证场景其实可以是同一用户)。 - Bob 客户端弹窗提示 Bob 接受密钥验证。Bob 接受后,其客户端发送 [`m.key.verification.ready`](#mkeyverificationready) 事件,载明其支持的验证方式。 - Alice 或 Bob 选择双方都支持的一个验证方式后,主设备发送 [`m.key.verification.start`](#mkeyverificationstart) 事件,标明所选验证方式。如果双方仅有唯一通用方式,则可自动选择。 - Alice 和 Bob 按所选验证方式完成验证,可能涉及客户端间消息交互,双方在带外渠道核对信息,或与各自设备交互。 - Alice 和 Bob 客户端各自发送 [`m.key.verification.done`](#mkeyverificationdone) 通知验证成功。 任一设备可随时通过发送 [`m.key.verification.cancel`](#mkeyverificationcancel) 事件取消验证,请求体里的 code 字段指明原因。 to-device 场景下,Alice 若无法确定要验证 Bob 的哪台设备或不想指定特定设备,可以向 Bob 所有设备发 `m.key.verification.request`,事务 ID 相同。当 Bob 某台设备接受或拒绝(发 `m.key.verification.ready` 或 `m.key.verification.cancel`)时,Alice 要向 Bob 其他设备发送 `m.key.verification.cancel`(若 Bob 接受则 code 为 `m.accepted`,拒绝则为 `m.user`)。假定 Alice、Bob 各有两台设备,Bob 第一台设备接受 Alice 第二台设备的请求,协议流程如下(注意,Alice 第一个设备未参与验证,消息次序 Bob、Alice 谁先发 `m.key.verification.start` 都可以;最终双方互发 done 通知): ``` +---------------+ +---------------+ +-------------+ +-------------+ | AliceDevice1 | | AliceDevice2 | | BobDevice1 | | BobDevice2 | +---------------+ +---------------+ +-------------+ +-------------+ | | | | | | m.key.verification.request | | | |---------------------------------->| | | | | | | | m.key.verification.request | | | |-------------------------------------------------->| | | | | | | m.key.verification.ready | | | |<----------------------------------| | | | | | | | m.key.verification.cancel | | | |-------------------------------------------------->| | | | | | | m.key.verification.start | | | |<----------------------------------| | | | | | . . (verification messages) . | | | | | | m.key.verification.done | | | |<----------------------------------| | | | | | | | m.key.verification.done | | | |---------------------------------->| | | | | | ``` 房间消息下,Alice 只需向房间发送一次请求事件(类型为 `m.room.message` 且 `msgtype: m.key.verification.request`),而非向 Bob 各设备分别发送 `m.key.verification.request`。一旦 Bob 某设备发出 `m.key.verification.ready`,其他 Bob 设备即可知有设备已接受,自动忽略该请求,无须 Alice 主动通知。 房间加密模式下,验证流程不得因加密受阻。例如:若验证消息本身用加密,务必确保收件人所有未经验证的设备都能获得解密该消息的密钥(即使通常不会给他们解密密钥)。或者,也可将验证消息设为明文(但并不推荐)。 Bob 某设备收到 Alice 的 `m.key.verification.request` 且所有方法都不支持时,不应直接取消请求,以免其他设备支持。应提示 Bob 无支持方式,允许其手动拒绝。 提示 Bob 接受/拒绝 Alice 请求(或“不支持”提示)应在 `timestamp`(to-device)或 `origin_ts`(房间消息)后 10 分钟,或收到消息后 2 分钟(以先到者为准)自动消失,期间若收到 `m.key.verification.cancel` 则自动隐藏。 Bob 若拒绝请求,客户端需发送 code 为 `m.user` 的 `m.key.verification.cancel`,Alice 应知晓被拒绝,并若是 to-device 消息类型,还要通知 Bob 的所有设备该请求已被拒绝。 Alice/Bob 若同时发出 `m.key.verification.start` 且指定同一验证方式,用户 ID 字典序较大的设备消息被忽略,视为只有较小字典序一端发起。若用户 ID 相同(即自验证),则比对设备 ID。若指定验证方式不一致,则以 `code: m.unexpected_message` 取消。 to-device 验证也可无请求直接发 `m.key.verification.start`,该行为已废弃,建议新客户端不要这样做,但应能兼容老客户端。 具体验证方式可能会添加额外步骤、事件及消息字段。定义于本规范下的方法事件类型须以 `m.key.verification` 命名空间为前缀,其他自定义事件按 Java 包命名原则命名。 {{% event event="m.room.message$m.key.verification.request" title="`m.room.message` with `msgtype: m.key.verification.request`" %}} {{% event event="m.key.verification.request" %}} {{% event event="m.key.verification.ready" %}} {{% event event="m.key.verification.start" %}} {{% event event="m.key.verification.done" %}} {{% event event="m.key.verification.cancel" %}} ##### 短认证字符串(SAS)验证 SAS 验证是一种基于上述通用框架的用户友好密钥验证流程。其高度交互化,使用户容易参与其中。 验证过程受 Phil Zimmermann 的 ZRTP 密钥协商启发。其关键在于哈希承诺(commitment):发起 Diffie-Hellman 的一方先发送自己部分的哈希,只有收到对方部分才发明文。这样,攻击者只会有一次机会破解 DH,实际上即便只校验 n 位也能保证高安全性:若认证 n 位,攻击者成功概率为 1/2ⁿ。如验证 40 位,攻击概率小于十万亿分之一,若失败,两端的短认证字符串会不一致,从而提醒用户遭遇攻击。 要支持该方式,客户端应在 `m.key.verification.request` 和 `m.key.verification.ready` 的 `methods` 字段中声明名称 `m.sas.v1`。 SAS 验证主要两步: 1. 密钥协商(类似 [ZRTP 协商](https://tools.ietf.org/html/rfc6189#section-4.4.1))。 2. 密钥认证(基于 HMAC)。 Alice 和 Bob 互验证流程如下: 1. Alice 和 Bob 建立安全的带外连接,如线下、视频通话等。(“安全”即无可被冒充,而非完全机密。) 2. 双方启动密钥验证流程。 3. Alice 设备向 Bob 设备发 `m.key.verification.start`,确保 Bob 的设备密钥已本地存储。 4. Bob 设备收到后,选定双方均支持的密钥协商、哈希、MAC、SAS 算法。 5. Bob 设备确保 Alice 设备密钥也齊。 6. Bob 设备生成临时 Curve25519 密钥对 (*K_B^private*, *K_B^public*),计算公钥哈希。 7. Bob 回复 `m.key.verification.accept`,传递承诺。 8. Alice 保存承诺哈希备用。 9. Alice 设备生成临时 Curve25519 密钥对 (*K_A^private*, *K_A^public*), 以 `m.key.verification.key` 只发送公钥。 10. Bob 回复 `m.key.verification.key`,含其公钥。 11. Alice 校验 Bob 设备发来的密钥哈希与早先的承诺及 Alice 启动消息内容一致。 12. 双方用自己的私有临时密钥与对方的公钥做 ECDH,得共享密钥。 13. 双方用选定算法从共享密钥派生 SAS(短认证字符串)。如有多种方式,客户端应允许用户选择。 14. 两端人工比对 SAS,手动告知设备是否一致。 15. 若认证一致,双方各自针对下述密钥做 MAC 认证: * 希望对方验证的所有密钥(通常是各自设备 ed25519 密钥及主跨签名密钥)。 * 希望对方验证密钥的 key ID 列表。 MAC 算法见[下文](#mac-calculation)。 16. 双方并行发送 `m.key.verification.mac`,各自附上密钥及 ID 的 MAC 值。 17. 对端收到 `m.key.verification.mac` 后,本地核算 MAC 进行比对,一致则设备密钥通过认证。 18. 双方各自发送 `m.key.verification.done` 完成验证。 Alice/Bob 设备间协议交互如下: ``` +-------------+ +-----------+ | AliceDevice | | BobDevice | +-------------+ +-----------+ | | | m.key.verification.start | |-------------------------------->| | | | m.key.verification.accept | |<--------------------------------| | | | m.key.verification.key | |-------------------------------->| | | | m.key.verification.key | |<--------------------------------| | | | m.key.verification.mac | |-------------------------------->| | | | m.key.verification.mac | |<--------------------------------| | | ``` ###### 错误与异常处理 过程中出现错误处理如下: - Alice 或 Bob 任何时刻都可取消验证。须发送 `m.key.verification.cancel`。 - 超时。验证流程超时设为 10 分钟,事务 ID 长时间(10 分钟未收发)未用也需过期,用户应看到超时提示,同时通知对方取消。 - 一台设备参与多次验证,接收端需取消与该设备的所有未完成尝试。 - 若收到未知事务 ID,须给对方发送说明原因的 `m.key.verification.cancel`(首次收到 `m.key.verification.start` 或 `m.key.verification.cancel` 除外)。 - 若双方无共同密钥、哈希、HMAC 或 SAS 算法,则发錯誤 `m.key.verification.cancel`。 - 用户主观判定 SAS 不一致时,需发送 `m.key.verification.cancel`。 - 若收到意外次序的消息,应通知对方并取消。 ###### SAS 验证专用消息 通用验证框架基础上,SAS 用到如下事件。 `m.key.verification.cancel` 事件无需变更,但新增以下错误码: - `m.unknown_method`:双方无法协商出共同算法。 - `m.mismatched_commitment`:哈希承诺校验失败。 - `m.mismatched_sas`:SAS 不一致。 {{% event event="m.key.verification.start$m.sas.v1" title="`m.key.verification.start` with `method: m.sas.v1`" %}} {{% event event="m.key.verification.accept" %}} {{% event event="m.key.verification.key" %}} {{% event event="m.key.verification.mac" %}} ###### MAC 计算 验证期间,会对密钥以及密钥 ID 列表生成 MAC。 采用的 MAC 算法由 [`m.key.verification.accept`](#mkeyverificationaccept) 的 `message_authentication_code` 字段决定。当前要求使用 `hkdf-hmac-sha256.v2`,实现如下: 1. 用 HKDF([RFC5869](https://tools.ietf.org/html/rfc5869),哈希为 SHA-256)生成 HMAC 密钥。输入为共享密钥。无盐值,info 参数为下列组合: - 字符串 `MATRIX_KEY_VERIFICATION_MAC` - 被 MAC 密钥所属用户的 Matrix ID - 发送 MAC 的设备 ID - 另一用户的 Matrix ID - 接收 MAC 的设备 ID - 正在使用中的 `transaction_id` - 被 MAC 密钥的 Key ID,若为密钥列表则为字符串 `KEY_IDS` 2. 用上述密钥和 SHA-256 按 [RFC2104](https://tools.ietf.org/html/rfc2104) 做 HMAC 得 MAC 值。 若为密钥,则对密钥的公钥编码作 MAC,如 `ed25519` 算法下为未填充 base64 格式; 若为密钥 ID 列表,则须字典序排序、以逗号分隔(无额外空格),每个格式如 `{algorithm}:{keyId}`,如:`ed25519:Cross+Signing+Key,ed25519:DEVICEID`。此设计保证接收方比对 mac 字段名时即可还原全体 Key ID,无新增删漏。 3. MAC 最后以 base64 编码,并发送于 [`m.key.verification.mac`](#mkeyverificationmac) 事件。 {{% boxes/note %}} 曾用的 `hkdf-hmac-sha256` MAC 方法的 base64 编码错误,因 libolm 最初实现缺陷。新版本 `hkdf-hmac-sha256.v2` 与之算法相同,但采用标准 base64 编码。`hkdf-hmac-sha256` 已废弃,日后将移除。若双方都支持 `hkdf-hmac-sha256.v2`,则绝不可使用 `hkdf-hmac-sha256`。 {{% /boxes/note %}} ###### SAS HKDF 计算 所有 SAS 方法均按 [RFC5869](https://tools.ietf.org/html/rfc5869) HKDF 标准实现,哈希算法取决于之前协商,输入为协商得的共享密钥,无盐: 当 `key_agreement_protocol` 为 `curve25519-hkdf-sha256` 时,info 参数为: - 字符串 `MATRIX_KEY_VERIFICATION_SAS|` - 启动 `m.key.verification.start` 消息用户 Matrix ID,后跟 `|` - 启动消息设备 ID,后跟 `|` - 该设备发出的 `m.key.verification.key` 公钥,未填充 base64,后跟 `|` - 接受 `m.key.verification.accept` 消息用户 Matrix ID,后跟 `|` - 该设备 ID,后跟 `|` - 该设备发出的 `m.key.verification.key` 公钥,未填充 base64,后跟 `|` - 当前用的 `transaction_id` 若为废弃的 `curve25519`,info 依次为: - 字符串 `MATRIX_KEY_VERIFICATION_SAS` - 启动用户 Matrix ID - 启动设备 ID - 接受用户 Matrix ID - 接受设备 ID - `transaction_id` 不建议新实现支持旧方法 `curve25519`。 {{% boxes/rationale %}} HKDF 能让密钥协商更安全且易于处理。 {{% /boxes/rationale %}} ###### SAS 方法:`decimal` 用 [HKDF](#sas-hkdf-calculation) 生成 5 字节,按 13 位一组转换成十进制数(范围 0~8191),各加 1000,得到 3 组认证码。 具体为: - 第一组:(*B₀*≪5|*B₁*≫3)+1000 - 第二组:( (*B₁*&0x7)≪10|*B₂*≪2|*B₃*≫6 )+1000 - 第三组:( (*B₃*&0x3F)≪7|*B₄*≫1 )+1000 用户两端显示时应加分隔符或分行。 ###### SAS 方法:`emoji` 用 [HKDF](#sas-hkdf-calculation) 生成 6 字节,将前 42 位分为 7 组(每组 6 位),参照 base64,每组 6 位得 0~63 数字,查下表转为表情: {{% sas-emojis %}} {{% boxes/note %}} 该表亦可由 JSON 下载: {{% /boxes/note %}} {{% boxes/rationale %}} 上述 Emoji 选择标准: - 无颜色即能辨识 - 小图依然清晰 - 各文化普遍识别 - 互不相似,不易误认 - 易用少量词描述 - 无负面含义、平台表现一致性高 {{% /boxes/rationale %}} 客户端应以所表描述或译文显示 Emoji。各客户端宜协作维护多语言译文集。 {{% boxes/note %}} 已知译文在 ,在线翻译由 {{% /boxes/note %}} ##### 跨签名 跨签名功能,允许用户只需一次验证,就能信任对方今后新添加的设备(而无需依次手工验证对方全部设备)。每个用户有一套跨签名密钥:\ - 主密钥(MSK):作为跨签名身份密钥,为用户用户主身份,对其他密钥签名; - 用户签名密钥(USK):仅本用户可见,用于为其他用户主密钥签名; - 自签名密钥(SSK):用于为本用户所有设备密钥签名。 主密钥还可为备份密钥签名。主密钥本身可用各自设备密钥签名,便于从设备验证迁移(如 Alice 先前已验证过 Bob 设备,Bob 设备已签主密钥,Alice 的设备可信任主密钥,并可用自己 USK 签之)。 用户通过 [POST /\_matrix/client/v3/keys/device_signing/upload](/client-server-api/#post_matrixclientv3keysdevice_signingupload) API 上传跨签名密钥。Alice 若上传新密钥,其用户 ID 会自动出现在所有与她共享加密房间的用户 `/sync` 响应的 `device_lists` 字段的 `changed` 属性中。看到 Alice ID 后,Bob 应用 [POST /\_matrix/client/v3/keys/query](/client-server-api/#post_matrixclientv3keysquery) 查询 Alice 的设备密钥与跨签名密钥。 如果 Alice 想给 Bob 发加密消息,则只要满足: - Alice 设备正用主密钥且已签 USK, - Alice USK 已签 Bob 主密钥, - Bob 主密钥已签其 SSK, - Bob 的 SSK 已签 Bob 的设备密钥, 即可信任 Bob 的设备。下图展示密钥签名关系: ``` +------------------+ .................. +----------------+ | +--------------+ | .................. : | +------------+ | | | v v v : : v v v | | | | +-----------+ : : +-----------+ | | | | | Alice MSK | : : | Bob MSK | | | | | +-----------+ : : +-----------+ | | | | | : : : : | | | | | +--+ :... : : ...: +--+ | | | | v v : : v v | | | | +-----------+ ............. : : ............. +-----------+ | | | | | Alice SSK | : Alice USK : : : : Bob USK : | Bob SSK | | | | | +-----------+ :...........: : : :...........: +-----------+ | | | | | ... | : : : : | ... | | | | | V V :........: :........: V V | | | | +---------+ -+ +---------+ -+ | | | | | Devices | ...| | Devices | ...| | | | | +---------+ -+ +---------+ -+ | | | | | ... | | ... | | | | +------+ | | +----+ | +----------------+ +--------------+ ``` 其中,方框代表密钥,箭头指示签名方向,虚线代表仅用户本人可见的键或签名。 Alice 视角下隐藏她不可见的密钥/签名: ``` +------------------+ +----------------+ +----------------+ | +--------------+ | | | | +------------+ | | | v v | v v v | | | | +-----------+ | +-----------+ | | | | | Alice MSK | | | Bob MSK | | | | | +-----------+ | +-----------+ | | | | | | | | | | | | +--+ +--+ | +--+ | | | | v v | v | | | | +-----------+ +-----------+ | +-----------+ | | | | | Alice SSK | | Alice USK | | | Bob SSK | | | | | +-----------+ +-----------+ | +-----------+ | | | | | ... | | | | ... | | | | | V V +--------+ V V | | | | +---------+ -+ +---------+ -+ | | | | | Devices | ...| | Devices | ...| | | | | +---------+ -+ +---------+ -+ | | | | | ... | | ... | | | | +------+ | | +----+ | +----------------+ +--------------+ ``` [验证方法](#device-verification) 也可直接用于验证主密钥:将其公钥(未填充 base64)做为“设备 ID”,按常规定义。例如,Alice 和 Bob 用 SAS 验证后,Alice 的 `m.key.verification.mac` 里可能有 `"ed25519:alices+master+public+key": "alices+master+public+key"`。因此服务器必须防止设备 ID 与跨签名公钥冲突。 私钥可借 [Secrets](#secrets) 模块储存在服务器或分发给其他设备。分别用 `m.cross_signing.master`, `m.cross_signing.user_signing`, `m.cross_signing.self_signing` 名称标识,采用 base64 编码后加密。 ###### 密钥与签名安全 主密钥一旦泄露,攻击者即可冒充用户或让用户信任冒名顶替者。因此主密钥私钥必须严格安全储存。若客户端无安全存储能力(如无系统级密钥保护),严禁保存主密钥私钥。 看到其他用户更换主密钥,应提醒用户再决定是否继续通信。 由于设备 key ID (`ed25519:DEVICE_ID`) 与跨签名 key ID (`ed25519:PUBLIC_KEY`) 处于同一命名空间,客户端必须区分并用公钥实际内容定位验证目标。 服务器虽应禁止设备 ID 与跨签名密钥冲突,但仍须防范恶意服务器,所以客户端应注意: 1. 在验证过程中用密钥公钥而不是仅用 key ID 识别密钥; 2. 验证一开始就锁定目标密钥,并确保其在过程中未被更换; 3. 如发现用户下设备 ID 与跨签名 key 相同,必须拒绝验证并弹警告。 用户签名与自签名密钥意在可被快速替换(如有泄露,仅需主密钥重签及适度重新验证)。但发现被攻破和重签都要耗用户精力,因此应优先安全存储私钥,否则干脆不存储,以权衡安全与便用。 为防社交网络泄露,服务器仅允许用户查阅: - 自己主密钥/自签/用户签名密钥的签名, - 自己设备对自己主密钥的签名, - 他人自签密钥对其设备的签名, - 他人主密钥对其自签密钥的签名, - 他人设备对其主密钥的签名。 用户无法看到其他人 user-signing key 的签名。 {{% http-api spec="client-server" api="cross_signing" %}} ##### 二维码 {{% added-in v="1.1" %}} 二维码验证适用于一方设备支持扫码,可快速验证。二维码内容编码双方主签名密钥及一组随机共享密钥供单次扫描双向验证。 若支持显示二维码,客户端在 `m.key.verification.request` 和 `m.key.verification.ready` 的 `methods` 字段中声明 `m.qr_code.show.v1` 和 `m.reciprocate.v1`。若支持扫码,则用 `m.qr_code.scan.v1` 和 `m.reciprocate.v1`。如果既支持显示又支持扫码,则全部声明。 Alice、Bob 互验证流程: 1. Alice 和 Bob 面对面,欲验证钥匙。 2. 启动验证流程。 3. Alice 客户端显示二维码,若 Bob 客户端能扫码则 Bob 可选扫码;Bob 客户端也可以显示二维码或选择扫码。二维码格式见下文。其他验证方式如 SAS Emoji 可并列提供。 5. Alice 扫描 Bob 的二维码。 6. Alice 设备校验扫码内容是否为期望的公钥。如失败则报错同时发送 `m.key.verification.cancel` 给 Bob。 否则 - 此时 Alice 已信任 Bob 密钥, - 并知道 Bob 拥有 Alice 的正确密钥。 接下来 Alice 需告知 Bob 验证结果以使其信任 Alice 密钥。 7. Alice 设备显示验证成功;Bob 设备尚未通过验证(需等待 Alice 明确反馈),用户需人工看到 Alice 已同意。 8. Alice 设备发送 `m.key.verification.start(method=m.reciprocate.v1)` 给 Bob,包含共享密钥。仅作信号用,无验证功效。 9. Bob 收到上述消息,校验共享密钥。如不符则报错(这不影响 Alice 对 Bob 的验证);如一致,则请求 Bob 手动确认已经被验证。 10. Bob 见 Alice 端确认密钥一致后,点按钮确认验证。 Bob 信任 Alice,依据于 Alice 现场告知已验证成功。恶意 Alice 欺骗只影响自己与 Bob 通信,Alice 没动力作假。因而只要通信媒介可靠(如真人言语),这一流程足以可信。 11. 双方退回 `m.key.verification.done`。 ###### 二维码格式 二维码必须兼容 [ISO/IEC 18004:2015](https://www.iso.org/standard/62021.html),仅使用字节模式单分段。 纠错级别由展示端自行选择。 二进制内容结构如下: - ASCII "MATRIX",各字节编码分别为 0x4D, 0x41, 0x54, 0x52, 0x49, 0x58 - 1 字节,二维码版本(须为 0x02) - 1 字节,二维码验证模式,值为: - 0x00 验证其他用户(跨签名) - 0x01 本端信任主密钥情况下自验证 - 0x02 本端尚未信任主密钥的自验证 - 关联验证事件的 event ID 或 transaction_id,方式为: - 2 字节,网络字节序,长度 - 实际 UTF-8 字符串编码的 ID - 第一个 32 字节密钥,含义: - 若 mode==0x00 或 0x01,则本用户主跨签名公钥 - 若 mode==0x02,则本设备 Ed25519 签名密钥 - 第二个 32 字节密钥,含义: - 若 mode==0x00,则为他人主跨签名公钥 - 若 mode==0x01,则为他人设备 Ed25519 公钥 - 若 mode==0x02,为本用户主跨签名公钥 - 一组随机共享密钥(推荐约 8 字节),为编码内容的剩余部分 实例:Alice 的二维码内容 ``` "MATRIX" |ver|mode| len | event ID 4D 41 54 52 49 58 02 00 00 2D 21 41 42 43 44 ... | 用户主密钥 | 对方主密钥 | 共享密钥 00 01 02 03 04 05 06 07 ... 10 11 12 13 14 15 16 17 ... 20 21 22 23 24 25 26 27 ``` 代表 Alice 验证 Bob,在事件 "$ABCD..." 下自认主密钥为 `0001020304050607...` (base64: "AAECAwQFBg..."),她认为 Bob 的主密钥为 `1011121314151617...` (base64: "EBESExQVFh..."),共享密钥为 `2021222324252627` (base64: "ICEiIyQlJic")。 ###### 二维码专用验证消息 {{% event event="m.key.verification.start$m.reciprocate.v1" title="`m.key.verification.start` with `method: m.reciprocate.v1`" %}} #### 设备间共享密钥 Bob 若在电脑端与 Alice 有加密对话,在手机端首次登入时,或希望能访问到历史消息。为此本协议支持若干跨设备密钥转移方式。 ##### 密钥请求 如果设备缺失解密信息所需的密钥,可向其他设备发 [m.room_key_request](#mroom_key_request) to-device 消息,请求字段 `action` 设为 `request`。 其他设备同意分享密钥后,可通过加密的 [m.forwarded_room_key](#mforwarded_room_key) to-device 消息发送给请求设备。请求设备收到后,需向所有原发出请求的设备发 `request_cancellation` 的 [m.room_key_request](#mroom_key_request) 通知取消,接收端一旦收到 `request_cancellation` 就应忽视所有同一 `request_id` 和 `requesting_device_id` 的早前请求。 如设备决定不共享密钥,可以发 [m.room_key.withheld](#mroom_keywithheld) 以反馈拒绝加密密钥分享,详见[报告加密密钥被拒绝](#reporting-that-decryption-keys-are-withheld)。 {{% boxes/note %}} 密钥分享本身是攻击点,必须谨慎对待。客户端应只向受信任同一用户的设备请求/发密钥,并只接受来自受信任同一用户设备的转发密钥。 {{% /boxes/note %}} ##### 服务器端密钥备份 设备可将加密密钥上传服务器做加密备份。需解密某消息时,设备可向服务器请求该密钥并解密。备份为每用户维护,用户可重置覆盖已有备份。 相比[密钥请求](#key-requests),服务器备份无需在线设备即可获取密钥。但会话密钥在服务器以加密形式存储,客户端需有[解密密钥](#decryption-key) 方可解密备份。 创建备份时,客户端调用 [POST /\_matrix/client/v3/room_keys/version](#post_matrixclientv3room_keysversion),加密方式定义于 `auth_data`。其他客户端用 [GET /\_matrix/client/v3/room_keys/version](#get_matrixclientv3room_keysversion) 发现备份。密钥随后按备份的 `auth_data` 加密,并用 [PUT /\_matrix/client/v3/room_keys/keys](#put_matrixclientv3room_keyskeys) 或其相关变体上传,可用 [GET /\_matrix/client/v3/room_keys/keys](#get_matrixclientv3room_keyskeys) 或变体下载。一次仅能写入最新备份版本。也可用 [DELETE /\_matrix/client/v3/room_keys/version/{version}](#delete_matrixclientv3room_keysversionversion) 或 [DELETE /\_matrix/client/v3/room_keys/keys](#delete_matrixclientv3room_keyskeys) 等方式删除备份或部分密钥。 客户端上传密钥前,须确保 `auth_data` 已被信任。可通过: - 检查其是否由用户主跨签名密钥或受信设备签名 - 或用私钥本地推导出并验证公钥是否一致。受信私钥获取方式包括用户自己输入、从[秘密存储](#secret-storage)读取,或经[秘密共享](#sharing)自本用户受信设备拉取密钥。 如果上传会话密钥已存在,服务器对比密钥元数据保留或覆盖,规则如下: - 若有 `is_verified` 字段,优先保留设为 `true` 的; - 若两者都同等,则选择 `first_message_index` 更小者; - 最后由 `forwarded_count` 更小者获保留。 ###### 解密密钥 通常,解密钥匙(即密钥的私有部分)会以 [Secrets](#secrets) 模块方式存储服务器或发送给其他设备,采用 `m.megolm_backup.v1` 名称,且加密前为 base64 编码。 如用户直接复制密钥,则以[通用加密密钥表示法](/appendices/#cryptographic-key-representation)为字符串展示。 {{% boxes/note %}} 本规范早前使用“恢复密钥”一词,但各客户端 UI 通常用该词指[秘密存储](#storage)密钥,为避免歧义,现不再使用该说法。 {{% /boxes/note %}} ###### 备份算法:`m.megolm_backup.v1.curve25519-aes-sha2` 若备份的 `algorithm` 为 `m.megolm_backup.v1.curve25519-aes-sha2`,则 `auth_data` 结构如下: {{% definition path="api/client-server/definitions/key_backup_auth_data" %}} 备份的 `session_data` 字段生成步骤: 1. 把要备份的会话密钥编码为 `BackedUpSessionData` 格式的 JSON 对象。 2. 生成临时 curve25519 密钥,并用它和备份公钥做 ECDH 得共享密钥。临时公钥(未填充 base64)存为 `ephemeral` 字段。 3. 以共享密钥做 HKDF(哈希算法为 SHA-256,盐为 32 字节全 0,info 为空字符串),得 80 字节。前 32 字节作 AES 密钥,下 32 字节作 MAC 密钥,后 16 字节作 AES 初始化向量。 4. JSON 序列化,加密方式为 AES-CBC-256/PKCS#7,密文未填充 base64 编码存于 `ciphertext`。 5. 用 MAC 密钥计算 HMAC-SHA-256(消息体为空),取前 8 字节转换为 base64 存为 `mac` 字段。 {{% boxes/warning %}} 第五步本意应对原始密文做 HMAC,但 libolm 实现失误导致实际传递空字符串。未来规范将修正此问题。参见 [MSC4048](https://github.com/matrix-org/matrix-spec-proposals/pull/4048)。 {{% /boxes/warning %}} {{% definition path="api/client-server/definitions/key_backup_session_data" %}} {{% http-api spec="client-server" api="key_backup" %}} ##### 密钥导出 密钥可手工导出成加密文件,经用户复制后导入另一设备。文件加密方式如下: 1. 会话数据以 [Key export format](#key-export-format) JSON 对象格式编码。 2. 用户口令经 PBKDF2(HMAC-SHA-512, password, S, N, 512) 推导 512 位密钥。S 为 128 比特随机盐,N 迭代次数(至少 10 万)。K, K' 为前后 256 比特,K 为 AES-256 密钥,K' 供 HMAC-SHA-256。 3. JSON 字符串化后用 AES-CTR-256/K 加密,IV 预设为 128 比特安全随机数且 bit 63 为零(兼容实现差异)。 4. 按顺序拼接如下: | 长度(字节)| 描述 | | ------------|-----------------------------------------------------------------------------------------| | 1 | 导出格式版本,须为 `0x01`。 | | 16 | 盐 S。 | | 16 | 初始化向量 IV。 | | 4 | N,32 位大端整数。 | | 变长 | 加密 JSON。 | | 32 | 以上所有数据用 K' 做 HMAC-SHA-256 后的结果。 | 5. 整体 base64 编码。可适当换行。 6. 最终整体包裹于 `-----BEGIN MEGOLM SESSION DATA-----\n` 和 `\n-----END MEGOLM SESSION DATA-----\n`。 ###### 密钥导出格式 导出会话为 `ExportedSessionData` JSON 数组,具体定义: {{% definition path="api/client-server/definitions/megolm_export_session_data" %}} #### 消息算法 ##### 消息算法命名 消息算法命名使用全规范内统一扩展格式。以 `m.` 开头的算法名保留为本规范定义。自定义算法应按 Java 包规范全局唯一命名。 算法名应简短清晰,并表明所用原始算法,以便判断安全性。例如: `m.olm.v1` 太短,识别度低且难于扩展。 `m.olm.v1.ecdh-curve25519-hdkfsha256.hmacsha256.hkdfsha256-aes256-cbc-hmac64sha256` 太冗长且损耗带宽、可读性差。 ##### `m.olm.v1.curve25519-aes-sha2` 此名代表 Olm 协议 v1,见 [Olm 规范](http://matrix.org/docs/spec/olm.html)。其组成: - Curve25519 初始密钥协商 - HKDF-SHA-256 密钥推进 - Curve25519 用作根密钥推进 - HMAC-SHA-256 推进主键 - HKDF-SHA-256,AES-256-CBC,8 字节 HMAC-SHA-256 验证加密 支持 Olm 的设备必须将 "m.olm.v1.curve25519-aes-sha2" 纳入支持算法列表,发布 Curve25519 设备密钥和一次性密钥。 一份 Olm 加密的事件: ```json { "type": "m.room.encrypted", "content": { "algorithm": "m.olm.v1.curve25519-aes-sha2", "sender_key": "", "ciphertext": { "": { "type": 0, "body": "" } } } } ``` ciphertext 为设备 Curve25519 公钥到加密负载的映射。body 为 Base64 编码的 Olm 消息内容。type 整数:0 为初始 key,1 为后续普通消息。 Olm 会话发送消息前,均为类型 0;收到对端消息后,转为类型 1。 客户端收到类型 0 消息,需先查是否已有匹配会话;若没有则新建并尝试解密,解密成功前不得持久化会话或销毁一次性密钥。 类型 1 需已有会话才能解密,若无会话,则视为无效消息。 明文负载如下: ```json { "type": "", "content": "", "sender": "", "recipient": "", "recipient_keys": { "ed25519": "" }, "keys": { "ed25519": "" } } ``` type 与 content 对应具体消息类型与内容。 这些字段用于防止攻击者冒名发 curve25519 公钥假冒自己。sender 应为发送者用户,recipient 为本地用户,recipient_keys 标明本端 ed25519。 客户端需确认加密消息正文中 `sender_key` 和解密明文中的 `keys.ed25519` 与 [`/keys/query`](#post_matrixclientv3keysquery) 查询到的密钥相符。还需核实密钥签名。否则无法确认发送设备拥有所声称 ed25519 私钥,这对经过验证设备尤为重要。 如与对方建立了多个会话,应选用最近收到且成功解密过消息的那个。若尚无消息则以建立时间论新旧。为避免膨胀,可为每个设备设一个最大会话数(至少 4),采用 LRU 策略淘汰旧会话。 ###### 恢复不可解密 Olm 消息 有时消息因各种原因无法解密。此时应假定 Olm 会话失效,需要新建会话。 {{% boxes/note %}} Megolm 加密消息通常不会这样。未解密的消息一般密钥随后可获得。但 Olm 无此恢复机制,故必须新建会话。 {{% /boxes/note %}} 新建会话时,需向对端发送 [m.dummy](#mdummy) 事件通知会话变更。 客户端应限速,不得一小时内反复新建会话。 可通过 `m.room_key_request` 请求帮助找回因失效而丢失的 Megolm 会话密钥。 {{% boxes/note %}} 对于未知 Megolm 会话的密钥请求,应广播给该用户的所有设备(不只当前 event 的 device_id 或 sender_key),因这些字段已废弃。见 [`m.megolm.v1.aes-sha2`](#mmegolmv1aes-sha2)。 {{% /boxes/note %}} ##### `m.megolm.v1.aes-sha2` {{% changed-in v="1.3" %}} `m.megolm.v1.aes-sha2` 代表 Megolm 算法 v1, 详见 [Megolm 规范](http://matrix.org/docs/spec/megolm.html)。详细: - HMAC-SHA-256 连续哈希推进 - HKDF-SHA-256,AES-256-CBC,加 8 字节 HMAC-SHA-256 验证 - Ed25519 签名消息 支持 Megolm 的设备即需支持 Olm,且将本算法纳入支持算法列表。 加密事件格式: ```json { "type": "m.room.encrypted", "content": { "algorithm": "m.megolm.v1.aes-sha2", "sender_key": "", "device_id": "", "session_id": "", "ciphertext": "" } } ``` 明文负载为: ```json { "type": "", "content": "", "room_id": "" } ``` room_id 写入明文防止服务器篡改房间。 客户端须防范重放攻击,记录 Megolm ratchet index,应拒绝重复 index 的消息(须避免误杀,如正常流程可能重复解密一条消息)。 同 Olm,客户端需确认消息发送人即明文 sender 字段,其房间与 session_id 已被本端信任记录。 {{% boxes/note %}} 自 `v1.3` 起,`sender_key` 与 `device_id` 字段已废弃。应继续发送,但不得用其校验消息来源。 客户端不得用这两字段存储/检索会话。 将来规范版本中,这些字段将完全移除。 {{% /boxes/note %}} {{% boxes/rationale %}} 移除此类字段,提升隐私和安全性,主设备不可见、降低对不受信数据(服务器可篡改、用户故意作假)的依赖。 session_id 本身全局唯一,无需额外 context 字段。 减少此依赖同时提升隐私与安全性。 {{% /boxes/rationale %}} 要在房间启用端到端加密,客户端可发 `m.room.encryption` 状态事件,`algorithm` 为 `m.megolm.v1.aes-sha2`。 房间新建 Megolm 会话后,需用 Olm 私信分发 session key 给目标设备,以便其解密今后消息。密钥由 `m.room_key` 事件发送。收到他人密钥后,须存储以便解密消息。 收到会话密钥时,必须确保密钥由 Olm channel 安全获得,以确认消息真实性。 当客户端要更新 Megolm 会话数据时,必须确保新数据仅来自可信来源(如本用户已验证设备的 `m.forwarded_room_key` 或 `m.room_key`),且新密钥的 message index 必须小于已有密钥。 #### 协议定义 ##### 事件 {{% event event="m.room.encryption" %}} {{% event event="m.room.encrypted" %}} {{% event event="m.room_key" %}} {{% event event="m.room_key_request" %}} {{% event event="m.forwarded_room_key" %}} {{% event event="m.dummy" %}} ##### 密钥管理 API {{% http-api spec="client-server" api="keys" %}} ##### /sync 扩展 {#e2e-extensions-to-sync} 本模块为 [`/sync`](/client-server-api/#get_matrixclientv3sync) 响应新增可选 `device_lists` 字段(详见下文)。仅增量 `/sync`(指定 `since` 参数时)需返回。客户端应在初始同步后用 [`/keys/query`](/client-server-api/#post_matrixclientv3keysquery) 或 [`/keys/changes`](/client-server-api/#get_matrixclientv3keyschanges) 跟进,见[跟踪用户设备列表](#tracking-the-device-list-for-a-user)。 同时新增 `device_one_time_keys_count` 属性。注意拼写与 [`/keys/upload`](/client-server-api/#post_matrixclientv3keysupload) 响应的 `one_time_key_counts` 有区别。 {{% added-in v="1.2" %}} 最后,新增 `device_unused_fallback_key_types` 列举当前设备已上传但尚未被领用的备用密钥算法。若某算法此前上传的备用密钥不在列表,则应在必要时上传替换密钥。此属性为强制包含项,亦可用于判断服务器对备用密钥的支持(如 `/versions` 外的备用判断方式)。 | 参数 | 类型 | 描述 | |-------------------------------|--------------------|----------------------------------------------------------------------------| | device_lists | DeviceLists | 可选。e2e 设备变更信息,仅在增量 sync 响应中出现。 | | device_one_time_keys_count | {string: integer} | 可选。按算法分列当前设备未被认领的一次性密钥数,未传视为 0。 | | device_unused_fallback_key_types | [string] | **必选。** 未被使用的备用密钥算法列表。 | `DeviceLists` | 参数 | 类型 | 描述 | |----------|-----------|--------------------------------------------------------------------------------------------------------| | changed | [string] | 设备身份或跨签名密钥有更新、或新近与本客户端共享加密房间的用户列表。 | | left | [string] | 自上次 sync 后,本客户端不再与之共享任何加密房间的用户列表。 | {{% boxes/note %}} 最优逻辑下,仅在 Alice 更新设备、密钥或与 Bob 新建共享房间时,在 Bob 的 sync 的 changed 字段添加 Alice。但为了简化,服务器也可在 Alice 与 Bob 每次新建共享房间(无论是否已共享)时添加。 {{% /boxes/note %}} 例: ```json { "next_batch": "s72595_4483_1934", "rooms": {"leave": {}, "join": {}, "invite": {}}, "device_lists": { "changed": [ "@alice:example.com", ], "left": [ "@bob:example.com", ], }, "device_one_time_keys_count": { "signed_curve25519": 20 }, "device_unused_fallback_key_types": ["signed_curve25519"] } ``` #### 报告密钥被拒绝 当客户端向房间内其他设备发送加密事件时,可选择通知那些因未提供密钥而无法解密事件的设备。如此接收方即可明确原因而非泛泛报错。 同理,一台设备通过[密钥请求](#key-requests) 向他设备请求密钥,对方亦可主动说明拒绝分享密钥。 如 Alice 一开始不愿向 Bob 分享部分消息的 Megolm 会话,后期决定给予 Bob 解密后续消息的权力,可发送只包含最新版会话密钥的消息,且在 Bob 的新设备请求密钥时,会获得被 ratchet(推进)过的最新版本。Bob 旧设备可在 `m.forwarded_room_key` 里以对象 `withheld` 字段附上最初被拒绝的原因代码。 {{% event event="m.room_key.withheld" %}}