End-to-End Encryption
Matrix optionally supports end-to-end encryption, allowing rooms to be created whose conversation contents are not decryptable or interceptable on any of the participating homeservers.
Key Distribution
Encryption and Authentication in Matrix is based around public-key cryptography. The Matrix protocol provides a basic mechanism for exchange of public keys, though an out-of-band channel is required to exchange fingerprints between users to build a web of trust.
Overview
- Bob publishes the public keys and supported algorithms for his device. This may include long-term identity keys, and/or one-time keys.
+----------+ +--------------+
| Bob's HS | | Bob's Device |
+----------+ +--------------+
| |
|<=============|
/keys/upload
- Alice requests Bob’s public identity keys and supported algorithms.
+----------------+ +------------+ +----------+
| Alice's Device | | Alice's HS | | Bob's HS |
+----------------+ +------------+ +----------+
| | |
|=================>|==============>|
/keys/query <federation>
- Alice selects an algorithm and claims any one-time keys needed.
+----------------+ +------------+ +----------+
| Alice's Device | | Alice's HS | | Bob's HS |
+----------------+ +------------+ +----------+
| | |
|=================>|==============>|
/keys/claim <federation>
Key algorithms
The name ed25519 corresponds to the
Ed25519 signature algorithm. The key is a
32-byte Ed25519 public key, encoded using unpadded Base64. Example:
"SogYyrkTldLz0BXP+GYWs0qaYacUI0RleEqNT8J3riQ"
The name curve25519 corresponds to the
Curve25519 ECDH algorithm. The key is a
32-byte Curve25519 public key, encoded using unpadded Base64.
Example:
"JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y"
The name signed_curve25519 also corresponds to the Curve25519
algorithm, but a key using this algorithm is represented by an object
with the following properties:
KeyObject
| Parameter | Type | Description |
|---|---|---|
| key | string | Required. The unpadded Base64-encoded 32-byte Curve25519 public key. |
| signatures | Signatures | Required. Signatures of the key object. The signature is calculated using the process described at Signing JSON. |
Example:
{
"key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8",
"signatures": {
"@user:example.com": {
"ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ"
}
}
}
Device keys
Each device should have one Ed25519 signing key. This key should be generated on the device from a cryptographically secure source, and the private part of the key should never be exported from the device. This key is used as the fingerprint for a device by other clients.
A device will generally need to generate a number of additional keys. Details of these will vary depending on the messaging algorithm in use.
Algorithms generally require device identity keys as well as signing keys. Some algorithms also require one-time keys to improve their secrecy and deniability. These keys are used once during session establishment, and are then thrown away.
For Olm version 1, each device requires a single Curve25519 identity key, and a number of signed Curve25519 one-time keys.
Uploading keys
A device uploads the public parts of identity keys to their homeserver
as a signed JSON object, using the /keys/upload API. The JSON object
must include the public part of the device’s Ed25519 key, and must be
signed by that key, as described in Signing
JSON.
One-time and fallback keys are also uploaded to the homeserver using the
/keys/upload API.
[Added in v1.2]
Fallback keys are similar to one-time keys, but are not consumed once used. They are only used when the device has run out of one-time keys, and can be replaced by a new fallback key.
Devices must store the private part of each key they upload. They can discard the private part of a one-time key when they receive a message using that key. However it’s possible that a one-time key given out by a homeserver will never be used, so the device that generates the key will never know that it can discard the key. Therefore a device could end up trying to store too many private keys. A device that is trying to store too many private keys may discard keys starting with the oldest.
Clients should not store the private half of fallback keys indefinitely to avoid situations where attackers can decrypt past messages sent using that fallback key.
Instead, clients should keep the private keys for at most 2 fallback keys: the current, unused, fallback key and the key immediately preceding it. Once the client is reasonably certain it has received all messages that used the old fallback key, such as after an hour since the first message, it should remove that fallback key.
Tracking the device list for a user
Before Alice can send an encrypted message to Bob, she needs a list of
each of his devices and the associated identity keys, so that she can
establish an encryption session with each device. This list can be
obtained by calling /keys/query, passing Bob’s user ID in the
device_keys parameter.
From time to time, Bob may add new devices, and Alice will need to know
this so that she can include his new devices for later encrypted
messages. A naive solution to this would be to call /keys/query
before sending each message -however, the number of users and devices
may be large and this would be inefficient.
It is therefore expected that each client will maintain a list of devices for a number of users (in practice, typically each user with whom we share an encrypted room). Furthermore, it is likely that this list will need to be persisted between invocations of the client application (to preserve device verification data and to alert Alice if Bob suddenly gets a new device).
Alice’s client can maintain a list of Bob’s devices via the following process:
- It first sets a flag to record that it is now tracking Bob’s device list, and a separate flag to indicate that its list of Bob’s devices is outdated. Both flags should be in storage which persists over client restarts.
- It then makes a request to
/keys/query, passing Bob’s user ID in thedevice_keysparameter. When the request completes, it stores the resulting list of devices in persistent storage, and clears the ‘outdated’ flag. - During its normal processing of responses to
/sync, Alice’s client inspects thechangedproperty of thedevice_listsfield. If it is tracking the device lists of any of the listed users, then it marks the device lists for those users outdated, and initiates another request to/keys/queryfor them. - Periodically, Alice’s client stores the
next_batchfield of the result from/syncin persistent storage. If Alice later restarts her client, it can obtain a list of the users who have updated their device list while it was offline by calling/keys/changes, passing the recordednext_batchfield as thefromparameter. If the client is tracking the device list of any of the users listed in the response, it marks them as outdated. It combines this list with those already flagged as outdated, and initiates a/keys/queryrequest for all of them.
Bob may update one of his devices while Alice has a request to
/keys/query in flight. Alice’s client may therefore see Bob’s user ID
in the device_lists field of the /sync response while the first
request is in flight, and initiate a second request to /keys/query.
This may lead to either of two related problems.
The first problem is that, when the first request completes, the client will clear the ‘outdated’ flag for Bob’s devices. If the second request fails, or the client is shut down before it completes, this could lead to Alice using an outdated list of Bob’s devices.
The second possibility is that, under certain conditions, the second request may complete before the first one. When the first request completes, the client could overwrite the later results from the second request with those from the first request.
Clients MUST guard against these situations. For example, a client could
ensure that only one request to /keys/query is in flight at a time for
each user, by queuing additional requests until the first completes.
Alternatively, the client could make a new request immediately, but
ensure that the first request’s results are ignored (possibly by
cancelling the request).
changed property of the device_lists field,
thus Bob will update his list of Alice’s devices as part of his normal
processing. Note that Bob can also be notified when he stops sharing any
room with Alice by inspecting the left property of the device_lists
field, and as a result should remove her from its list of tracked users.
Sending encrypted attachments
When encryption is enabled in a room, files should be uploaded encrypted on the homeserver.
In order to achieve this, a client should generate a single-use 256-bit AES key, and encrypt the file using AES-CTR. The counter should be 64-bit long, starting at 0 and prefixed by a random 64-bit Initialization Vector (IV), which together form a 128-bit unique counter block.
Then, the encrypted file can be uploaded to the homeserver. The key and
the IV must be included in the room event along with the resulting
mxc:// in order to allow recipients to decrypt the file. As the event
containing those will be Megolm encrypted, the server will never have
access to the decrypted file.
A hash of the ciphertext must also be included, in order to prevent the homeserver from changing the file content.
A client should send the data as an encrypted m.room.message event,
using either m.file as the msgtype, or the appropriate msgtype for the
file type. The key is sent using the JSON Web
Key format, with a
W3C extension.
Extensions to m.room.message msgtypes
This module adds file and thumbnail_file properties, of type
EncryptedFile, to m.room.message msgtypes that reference files, such
as m.file and m.image, replacing the url and thumbnail_url
properties.
EncryptedFile
| Parameter | Type | Description |
|---|---|---|
| url | string | Required. The URL to the file. |
| key | JWK | Required. A JSON Web Key object. |
| iv | string | Required. The 128-bit unique counter block used by AES-CTR, encoded as unpadded base64. |
| hashes | {string: string} | Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. Clients should support the SHA-256 hash, which uses the key sha256. |
| v | string | Required. Version of the encrypted attachments protocol. Must be v2. |
JWK
| Parameter | Type | Description |
|---|---|---|
| kty | string | Required. Key type. Must be oct. |
| key_ops | [string] | Required. Key operations. Must at least contain encrypt and decrypt. |
| alg | string | Required. Algorithm. Must be A256CTR. |
| k | string | Required. The key, encoded as urlsafe unpadded base64. |
| ext | boolean | Required. Extractable. Must be true. This is a W3C extension. |
Example:
{
"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
}
}
Claiming one-time keys
A client wanting to set up a session with another device can claim a
one-time key for that device. This is done by making a request to the
/keys/claim API.
A homeserver should rate-limit the number of one-time keys that a given user or remote server can claim. A homeserver should discard the public part of a one time key once it has given that key to another user.
[Added in v1.2]
If the device has run out of one-time keys which can be claimed, the homeserver will return the fallback key (if one was uploaded). Fallback keys are not deleted once used and should be replaced by the device when they are able to upload more one-time keys.
Device verification
Before Alice sends Bob encrypted data, or trusts data received from him, she may want to verify that she is actually communicating with him, rather than a man-in-the-middle. This verification process requires an out-of-band channel: there is no way to do it within Matrix without trusting the administrators of the homeservers.
In Matrix, verification works by Alice meeting Bob in person, or
contacting him via some other trusted medium, and using one of the
verification methods defined below to interactively verify Bob’s devices.
Alice and Bob may also read aloud their unpadded base64 encoded Ed25519
public key, as returned by /keys/query.
Device verification may reach one of several conclusions. For example:
- Alice may “accept” the device. This means that she is satisfied that the device belongs to Bob. She can then encrypt sensitive material for that device, and knows that messages received were sent from that device.
- Alice may “reject” the device. She will do this if she knows or suspects that Bob does not control that device (or equivalently, does not trust Bob). She will not send sensitive material to that device, and cannot trust messages apparently received from it.
- Alice may choose to skip the device verification process. She is not able to verify that the device actually belongs to Bob, but has no reason to suspect otherwise. The encryption protocol continues to protect against passive eavesdroppers.
Key verification framework
Verifying keys manually by reading out the Ed25519 key is not very user-friendly, and can lead to errors. In order to help mitigate errors, and to make the process easier for users, some verification methods are supported by the specification and use messages exchanged by the user’s devices to assist in the verification. The methods all use a common framework for negotiating the key verification.
Verification messages can be sent either in a room shared by the two parties, which should be a direct messaging room between the two parties, or by using to-device messages sent directly between the two devices involved. In both cases, the messages exchanged are similar, with minor differences as detailed below. Verifying between two different users should be performed using in-room messages, whereas verifying two devices belonging to the same user should be performed using to-device messages.
A key verification session is identified by an ID that is established by the
first message sent in that session. For verifications using in-room messages,
the ID is the event ID of the initial message, and for verifications using
to-device messages, the first message contains a transaction_id field that is
shared by the other messages of that session.
In general, verification operates as follows:
- Alice requests a key verification with Bob by sending an
m.key.verification.requestevent. This event indicates the verification methods that Alice’s client supports. (Note that “Alice” and “Bob” may in fact be the same user, in the case where a user is verifying their own devices.) - Bob’s client prompts Bob to accept the key verification. When Bob accepts
the verification, Bob’s client sends an
m.key.verification.readyevent. This event indicates the verification methods, corresponding to the verification methods supported by Alice’s client, that Bob’s client supports. - Alice’s or Bob’s devices allow their users to select one of the verification
methods supported by both devices to use for verification. When Alice or Bob
selects a verification method, their device begins the verification by
sending an
m.key.verification.startevent, indicating the selected verification method. Note that if there is only one verification method in common between the devices then the receiver’s device (Bob) can auto-select it. - Alice and Bob complete the verification as defined by the selected verification method. This could involve their clients exchanging messages, Alice and Bob exchanging information out-of-band, and/or Alice and Bob interacting with their devices.
- Alice’s and Bob’s clients send
m.key.verification.doneevents to indicate that the verification was successful.
Verifications can be cancelled by either device at any time by sending an
m.key.verification.cancel event with a code field that indicates the reason
it was cancelled.
When using to-device messages, Alice may not know which of Bob’s devices to
verify, or may not want to choose a specific device. In this case, Alice will
send m.key.verification.request events to all of Bob’s devices. All of these
events will use the same transaction ID. When Bob accepts or declines the
verification on one of his devices (sending either an
m.key.verification.ready or m.key.verification.cancel event), Alice will
send an m.key.verification.cancel event to Bob’s other devices with a code
of m.accepted in the case where Bob accepted the verification, or m.user in
the case where Bob rejected the verification. This yields the following
handshake when using to-device messages, assuming both Alice and Bob each have
2 devices, Bob’s first device accepts the key verification request, and Alice’s
second device initiates the request. Note how Alice’s first device is not
involved in the request or verification process. Also note that, although in
this example, Bob’s device sends the m.key.verification.start, Alice’s device
could also send that message. As well, the order of the
m.key.verification.done messages could be reversed.
+---------------+ +---------------+ +-------------+ +-------------+
| 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 | |
| |---------------------------------->| |
| | | |
When using in-room messages and the room has encryption enabled, clients should ensure that encryption does not hinder the verification. For example, if the verification messages are encrypted, clients must ensure that all the recipient’s unverified devices receive the keys necessary to decrypt the messages, even if they would normally not be given the keys to decrypt messages in the room. Alternatively, verification messages may be sent unencrypted, though this is not encouraged.
Upon receipt of Alice’s m.key.verification.request message, if Bob’s device
does not understand any of the methods, it should not cancel the request as one
of his other devices may support the request. Instead, Bob’s device should tell
Bob that no supported method was found, and allow him to manually reject the
request.
The prompt for Bob to accept/reject Alice’s request (or the unsupported method
prompt) should be automatically dismissed 10 minutes after the timestamp (in
the case of to-device messages) or origin_ts (in the case of in-room
messages) field or 2 minutes after Bob’s client receives the message, whichever
comes first, if Bob does not interact with the prompt. The prompt should
additionally be hidden if an appropriate m.key.verification.cancel message is
received.
If Bob rejects the request, Bob’s client must send an
m.key.verification.cancel event with code set to m.user. Upon receipt,
Alice’s device should tell her that Bob does not want to verify her device and,
if the request was sent as a to-device message, send
m.key.verification.cancel messages to all of Bob’s devices to notify them
that the request was rejected.
If Alice’s and Bob’s clients both send an m.key.verification.start message,
and both specify the same verification method, then the
m.key.verification.start message sent by the user whose ID is the
lexicographically largest user ID should be ignored, and the situation should
be treated the same as if only the user with the lexicographically smallest
user ID had sent the m.key.verification.start message. In the case where the
user IDs are the same (that is, when a user is verifying their own device),
then the device IDs should be compared instead. If the two
m.key.verification.start messages do not specify the same verification
method, then the verification should be cancelled with a code of
m.unexpected_message.
An m.key.verification.start message can also be sent independently of any
request, specifying the verification method to use. This behaviour is
deprecated, and new clients should not begin verifications in this way.
However, clients should handle such verifications started by other clients.
Individual verification methods may add additional steps, events, and
properties to the verification messages. Event types for methods defined
in this specification must be under the m.key.verification namespace
and any other event types must be namespaced according to the Java
package naming convention.
m.key.verification.request
Requests a key verification with another user’s devices.
m.key.verification.request
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
from_device |
string |
Required: The device ID which is initiating the request. |
methods |
[string] |
Required: The verification methods supported by the sender. |
timestamp |
integer |
Required when sent as a to-device message. The POSIX timestamp in milliseconds for when the request was made. If the request is in the future by more than 5 minutes or more than 10 minutes in the past, the message should be ignored by the receiver. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for the verification request. Must be unique with respect to the devices involved. |
Examples
{
"content": {
"from_device": "AliceDevice2",
"methods": [
"m.sas.v1"
],
"timestamp": 1559598944869,
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.request"
}
m.key.verification.ready
Accepts a key verification request. Sent in response to an
m.key.verification.request event.
m.key.verification.ready
m.key.verification.request event.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
from_device |
string |
Required: The device ID which is accepting the request. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
methods |
[string] |
Required: The verification methods supported by the sender, corresponding to
the verification methods indicated in the
m.key.verification.request message. |
transaction_id |
string |
Required when sent as a to-device message. The transaction ID of the
verification request, as given in the m.key.verification.request
message. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"from_device": "BobDevice1",
"methods": [
"m.sas.v1"
],
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.ready"
}
m.key.verification.start
Begins a key verification process. Typically sent as a to-device event. The method
field determines the type of verification. The fields in the event will differ depending
on the method. This definition includes fields that are in common among all variants.
m.key.verification.start
method
field determines the type of verification. The fields in the event will differ depending
on the method. This definition includes fields that are in common among all variants.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
from_device |
string |
Required: The device ID which is initiating the process. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
method |
string |
Required: The verification method to use. |
next_method |
string |
Optional method to use to verify the other user’s key with. Applicable
when the method chosen only verifies one user’s key. This field will
never be present if the method verifies keys both ways. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be unique with respect to the devices
involved. Must be the same as the transaction_id given in the
m.key.verification.request if this process is originating from a
request. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"from_device": "BobDevice1",
"method": "m.sas.v1",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.start"
}
{
"content": {
"from_device": "BobDevice1",
"hashes": [
"sha256"
],
"key_agreement_protocols": [
"curve25519"
],
"message_authentication_codes": [
"hkdf-hmac-sha256"
],
"method": "m.sas.v1",
"short_authentication_string": [
"decimal",
"emoji"
],
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.start"
}
m.key.verification.done
Indicates that a verification process/request has completed successfully.
m.key.verification.done
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
transaction_id |
string |
Required when sent as a to-device message. The opaque identifier for the verification process/request. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.done"
}
m.key.verification.cancel
Cancels a key verification process/request.
m.key.verification.cancel
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
code |
string |
Required: The error code for why the process/request was cancelled by the user. Error codes should use the Java package naming convention if not in the following list:
Clients should be careful to avoid error loops. For example, if a device sends
an incorrect message and the client returns |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
reason |
string |
Required: A human readable description of the code. The client should only rely on this
string if it does not understand the code. |
transaction_id |
string |
Required when sent as a to-device message. The opaque identifier for the verification process/request. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"code": "m.user",
"reason": "User rejected the key verification request",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.cancel"
}
Short Authentication String (SAS) verification
SAS verification is a user-friendly key verification process built off the common framework outlined above. SAS verification is intended to be a highly interactive process for users, and as such exposes verification methods which are easier for users to use.
The verification process is heavily inspired by Phil Zimmermann’s ZRTP key agreement handshake. A key part of key agreement in ZRTP is the hash commitment: the party that begins the Diffie-Hellman key sharing sends a hash of their part of the Diffie-Hellman exchange, and does not send their part of the Diffie-Hellman exchange until they have received the other party’s part. Thus an attacker essentially only has one attempt to attack the Diffie-Hellman exchange, and hence we can verify fewer bits while still achieving a high degree of security: if we verify n bits, then an attacker has a 1 in 2n chance of success. For example, if we verify 40 bits, then an attacker has a 1 in 1,099,511,627,776 chance (or less than 1 in 1012 chance) of success. A failed attack would result in a mismatched Short Authentication String, alerting users to the attack.
To advertise support for this method, clients use the name m.sas.v1 in the
methods fields of the m.key.verification.request and
m.key.verification.ready events.
The verification process takes place in two phases:
- Key agreement phase (based on ZRTP key agreement).
- Key verification phase (based on HMAC).
The process between Alice and Bob verifying each other would be:
- Alice and Bob establish a secure out-of-band connection, such as meeting in-person or a video call. “Secure” here means that either party cannot be impersonated, not explicit secrecy.
- Alice and Bob begin a key verification using the key verification framework as described above.
- Alice’s device sends Bob’s device an
m.key.verification.startmessage. Alice’s device ensures it has a copy of Bob’s device key. - Bob’s device receives the message and selects a key agreement protocol, hash algorithm, message authentication code, and SAS method supported by Alice’s device.
- Bob’s device ensures it has a copy of Alice’s device key.
- Bob’s device creates an ephemeral Curve25519 key pair (KBprivate, KBpublic), and calculates the hash (using the chosen algorithm) of the public key KBpublic.
- Bob’s device replies to Alice’s device with an
m.key.verification.acceptmessage. - Alice’s device receives Bob’s message and stores the commitment hash for later use.
- Alice’s device creates an ephemeral Curve25519 key pair
(KAprivate, KApublic)
and replies to Bob’s device with an
m.key.verification.key, sending only the public key KApublic. - Bob’s device receives Alice’s message and replies with its own
m.key.verification.keymessage containing its public key KBpublic. - Alice’s device receives Bob’s message and verifies the commitment
hash from earlier matches the hash of the key Bob’s device just sent
and the content of Alice’s
m.key.verification.startmessage. - Both Alice and Bob’s devices perform an Elliptic-curve Diffie-Hellman (ECDH(KAprivate, KBpublic)), using the result as the shared secret.
- Both Alice and Bob’s devices display a SAS to their users, which is derived from the shared key using one of the methods in this section. If multiple SAS methods are available, clients should allow the users to select a method.
- Alice and Bob compare the strings shown by their devices, and tell their devices if they match or not.
- Assuming they match, Alice and Bob’s devices calculate the HMAC of their own device keys and a comma-separated sorted list of the key IDs that they wish the other user to verify, using SHA-256 as the hash function. HMAC is defined in RFC 2104. The key for the HMAC is different for each item and is calculated by generating 32 bytes (256 bits) using the key verification HKDF.
- Alice’s device sends Bob’s device an
m.key.verification.macmessage containing the MAC of Alice’s device keys and the MAC of her key IDs to be verified. Bob’s device does the same for Bob’s device keys and key IDs concurrently with Alice. - When the other device receives the
m.key.verification.macmessage, the device calculates the HMAC of its copies of the other device’s keys given in the message, as well as the HMAC of the comma-separated, sorted, list of key IDs in the message. The device compares these with the HMAC values given in the message, and if everything matches then the device keys are verified. - Alice and Bob’s devices send
m.key.verification.donemessages to complete the verification.
The wire protocol looks like the following between Alice and Bob’s devices:
+-------------+ +-----------+
| 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 |
|<--------------------------------|
| |
Error and exception handling
At any point the interactive verification can go wrong. The following describes what to do when an error happens:
- Alice or Bob can cancel the verification at any time. An
m.key.verification.cancelmessage must be sent to signify the cancellation. - The verification can time out. Clients should time out a
verification that does not complete within 10 minutes. Additionally,
clients should expire a
transaction_idwhich goes unused for 10 minutes after having last sent/received it. The client should inform the user that the verification timed out, and send an appropriatem.key.verification.cancelmessage to the other device. - When the same device attempts to initiate multiple verification attempts, the recipient should cancel all attempts with that device.
- When a device receives an unknown
transaction_id, it should send an appropriatem.key.verification.cancelmessage to the other device indicating as such. This does not apply for inboundm.key.verification.startorm.key.verification.cancelmessages. - If the two devices do not share a common key share, hash, HMAC, or
SAS method then the device should notify the other device with an
appropriate
m.key.verification.cancelmessage. - If the user claims the Short Authentication Strings do not match,
the device should send an appropriate
m.key.verification.cancelmessage to the other device. - If the device receives a message out of sequence or that it was not
expecting, it should notify the other device with an appropriate
m.key.verification.cancelmessage.
Verification messages specific to SAS
Building off the common framework, the following events are involved in SAS verification.
The m.key.verification.cancel event is unchanged, however the
following error codes are used in addition to those already specified:
m.unknown_method: The devices are unable to agree on the key agreement, hash, MAC, or SAS method.m.mismatched_commitment: The hash commitment did not match.m.mismatched_sas: The SAS did not match.
m.key.verification.start$m.sas.v1
Begins a SAS key verification process using the m.sas.v1 method.
m.key.verification.start$m.sas.v1
m.sas.v1 method.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
from_device |
string |
Required: The device ID which is initiating the process. |
hashes |
[string] |
Required: The hash methods the sending device understands. Must include at least
sha256. |
key_agreement_protocols |
[string] |
Required: The key agreement protocols the sending device understands. Should
include at least curve25519-hkdf-sha256. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
message_authentication_codes |
[string] |
Required: The message authentication codes that the sending device understands.
Must include at least hkdf-hmac-sha256. |
method |
enum |
Required: The verification method to use. One of: |
short_authentication_string |
[string] |
Required: The SAS methods the sending device (and the sending device’s user)
understands. Must include at least decimal. Optionally can include
emoji. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be unique with respect to the devices
involved. Must be the same as the transaction_id given in the
m.key.verification.request if this process is originating from a
request. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
m.key.verification.accept
Accepts a previously sent m.key.verification.start message.
m.key.verification.accept
m.key.verification.start message.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
commitment |
string |
Required: The hash (encoded as unpadded base64) of the concatenation of the device’s
ephemeral public key (encoded as unpadded base64) and the canonical JSON
representation of the m.key.verification.start message. |
hash |
string |
Required: The hash method the device is choosing to use, out of the options in
the m.key.verification.start message. |
key_agreement_protocol |
string |
Required: The key agreement protocol the device is choosing to use, out of the
options in the m.key.verification.start message. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
message_authentication_code |
string |
Required: The message authentication code the device is choosing to use, out of
the options in the m.key.verification.start message. |
short_authentication_string |
[string] |
Required: The SAS methods both devices involved in the verification process
understand. Must be a subset of the options in the m.key.verification.start
message. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be the same as the one used for the
m.key.verification.start message. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"commitment": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg",
"hash": "sha256",
"key_agreement_protocol": "curve25519",
"message_authentication_code": "hkdf-hmac-sha256",
"method": "m.sas.v1",
"short_authentication_string": [
"decimal",
"emoji"
],
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.accept"
}
m.key.verification.key
Sends the ephemeral public key for a device to the partner device.
m.key.verification.key
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
key |
string |
Required: The device’s ephemeral public key, encoded as unpadded base64. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be the same as the one used for the
m.key.verification.start message. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"key": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.key"
}
m.key.verification.mac
Sends the MAC of a device’s key to the partner device.
m.key.verification.mac
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
keys |
string |
Required: The MAC of the comma-separated, sorted, list of key IDs given in the mac
property, encoded as unpadded base64. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
mac |
object |
Required: A map of the key ID to the MAC of the key, using the algorithm in the verification process. The MAC is encoded as unpadded base64. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be the same as the one used for the
m.key.verification.start message. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
{
"content": {
"keys": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA",
"mac": {
"ed25519:ABCDEF": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
},
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.mac"
}
HKDF calculation
In all of the SAS methods, HKDF is as defined in RFC
5869 and uses the previously
agreed-upon hash function for the hash function. The shared secret is
supplied as the input keying material. No salt is used. When the
key_agreement_protocol is curve25519-hkdf-sha256, the info parameter
is the concatenation of:
- The string
MATRIX_KEY_VERIFICATION_SAS|. - The Matrix ID of the user who sent the
m.key.verification.startmessage, followed by|. - The Device ID of the device which sent the
m.key.verification.startmessage, followed by|. - The public key from the
m.key.verification.keymessage sent by the device which sent them.key.verification.startmessage, followed by|. - The Matrix ID of the user who sent the
m.key.verification.acceptmessage, followed by|. - The Device ID of the device which sent the
m.key.verification.acceptmessage, followed by|. - The public key from the
m.key.verification.keymessage sent by the device which sent them.key.verification.acceptmessage, followed by|. - The
transaction_idbeing used.
When the key_agreement_protocol is the deprecated method curve25519,
the info parameter is the concatenation of:
- The string
MATRIX_KEY_VERIFICATION_SAS. - The Matrix ID of the user who sent the
m.key.verification.startmessage. - The Device ID of the device which sent the
m.key.verification.startmessage. - The Matrix ID of the user who sent the
m.key.verification.acceptmessage. - The Device ID of the device which sent the
m.key.verification.acceptmessage. - The
transaction_idbeing used.
New implementations are discouraged from implementing the curve25519
method.
For verification of each party’s device keys, HKDF is as defined in RFC 5869 and uses SHA-256 as the hash function. The shared secret is supplied as the input keying material. No salt is used, and in the info parameter is the concatenation of:
- The string
MATRIX_KEY_VERIFICATION_MAC. - The Matrix ID of the user whose key is being MAC-ed.
- The Device ID of the device sending the MAC.
- The Matrix ID of the other user.
- The Device ID of the device receiving the MAC.
- The
transaction_idbeing used. - The Key ID of the key being MAC-ed, or the string
KEY_IDSif the item being MAC-ed is the list of key IDs.
SAS method: decimal
Generate 5 bytes using HKDF then take sequences of 13 bits to convert to decimal numbers (resulting in 3 numbers between 0 and 8191 inclusive each). Add 1000 to each calculated number.
The bitwise operations to get the numbers given the 5 bytes B0, B1, B2, B3, B4 would be:
- First: (B0 ≪ 5|B1 ≫ 3) + 1000
- Second: ((B1&0x7) ≪ 10|B2 ≪ 2|B3 ≫ 6) + 1000
- Third: ((B3&0x3F) ≪ 7|B4 ≫ 1) + 1000
The digits are displayed to the user either with an appropriate separator, such as dashes, or with the numbers on individual lines.
SAS method: emoji
Generate 6 bytes using HKDF then split the first 42 bits into 7 groups of 6 bits, similar to how one would base64 encode something. Convert each group of 6 bits to a number and use the following table to get the corresponding emoji:
| Number | Emoji | Unicode | Description |
|---|---|---|---|
| 0 | 🐶 | U+1F436 | Dog |
| 1 | 🐱 | U+1F431 | Cat |
| 2 | 🦁 | U+1F981 | Lion |
| 3 | 🐎 | U+1F40E | Horse |
| 4 | 🦄 | U+1F984 | Unicorn |
| 5 | 🐷 | U+1F437 | Pig |
| 6 | 🐘 | U+1F418 | Elephant |
| 7 | 🐰 | U+1F430 | Rabbit |
| 8 | 🐼 | U+1F43C | Panda |
| 9 | 🐓 | U+1F413 | Rooster |
| 10 | 🐧 | U+1F427 | Penguin |
| 11 | 🐢 | U+1F422 | Turtle |
| 12 | 🐟 | U+1F41F | Fish |
| 13 | 🐙 | U+1F419 | Octopus |
| 14 | 🦋 | U+1F98B | Butterfly |
| 15 | 🌷 | U+1F337 | Flower |
| 16 | 🌳 | U+1F333 | Tree |
| 17 | 🌵 | U+1F335 | Cactus |
| 18 | 🍄 | U+1F344 | Mushroom |
| 19 | 🌏 | U+1F30F | Globe |
| 20 | 🌙 | U+1F319 | Moon |
| 21 | ☁️ | U+2601U+FE0F | Cloud |
| 22 | 🔥 | U+1F525 | Fire |
| 23 | 🍌 | U+1F34C | Banana |
| 24 | 🍎 | U+1F34E | Apple |
| 25 | 🍓 | U+1F353 | Strawberry |
| 26 | 🌽 | U+1F33D | Corn |
| 27 | 🍕 | U+1F355 | Pizza |
| 28 | 🎂 | U+1F382 | Cake |
| 29 | ❤️ | U+2764U+FE0F | Heart |
| 30 | 😀 | U+1F600 | Smiley |
| 31 | 🤖 | U+1F916 | Robot |
| 32 | 🎩 | U+1F3A9 | Hat |
| 33 | 👓 | U+1F453 | Glasses |
| 34 | 🔧 | U+1F527 | Spanner |
| 35 | 🎅 | U+1F385 | Santa |
| 36 | 👍 | U+1F44D | Thumbs Up |
| 37 | ☂️ | U+2602U+FE0F | Umbrella |
| 38 | ⌛ | U+231B | Hourglass |
| 39 | ⏰ | U+23F0 | Clock |
| 40 | 🎁 | U+1F381 | Gift |
| 41 | 💡 | U+1F4A1 | Light Bulb |
| 42 | 📕 | U+1F4D5 | Book |
| 43 | ✏️ | U+270FU+FE0F | Pencil |
| 44 | 📎 | U+1F4CE | Paperclip |
| 45 | ✂️ | U+2702U+FE0F | Scissors |
| 46 | 🔒 | U+1F512 | Lock |
| 47 | 🔑 | U+1F511 | Key |
| 48 | 🔨 | U+1F528 | Hammer |
| 49 | ☎️ | U+260EU+FE0F | Telephone |
| 50 | 🏁 | U+1F3C1 | Flag |
| 51 | 🚂 | U+1F682 | Train |
| 52 | 🚲 | U+1F6B2 | Bicycle |
| 53 | ✈️ | U+2708U+FE0F | Aeroplane |
| 54 | 🚀 | U+1F680 | Rocket |
| 55 | 🏆 | U+1F3C6 | Trophy |
| 56 | ⚽ | U+26BD | Ball |
| 57 | 🎸 | U+1F3B8 | Guitar |
| 58 | 🎺 | U+1F3BA | Trumpet |
| 59 | 🔔 | U+1F514 | Bell |
| 60 | ⚓ | U+2693 | Anchor |
| 61 | 🎧 | U+1F3A7 | Headphones |
| 62 | 📁 | U+1F4C1 | Folder |
| 63 | 📌 | U+1F4CC | Pin |
The emoji above were chosen to:
- Be recognisable without colour.
- Be recognisable at a small size.
- Be recognisable by most cultures.
- Be distinguishable from each other.
- Easily described by a few words.
- Avoid symbols with negative connotations.
- Be likely similar across multiple platforms.
Clients SHOULD show the emoji with the descriptions from the table, or appropriate translation of those descriptions. Client authors SHOULD collaborate to create a common set of translations for all languages.
Cross-signing
Rather than requiring Alice to verify each of Bob’s devices with each of her own devices and vice versa, the cross-signing feature allows users to sign their device keys such that Alice and Bob only need to verify once. With cross-signing, each user has a set of cross-signing keys that are used to sign their own device keys and other users’ keys, and can be used to trust device keys that were not verified directly.
Each user has three ed25519 key pairs for cross-signing:
- a master key (MSK) that serves as the user’s identity in cross-signing and signs their other cross-signing keys;
- a user-signing key (USK) – only visible to the user that it belongs to –that signs other users’ master keys; and
- a self-signing key (SSK) that signs the user’s own device keys.
The master key may also be used to sign other items such as the backup key. The master key may also be signed by the user’s own device keys to aid in migrating from device verifications: if Alice’s device had previously verified Bob’s device and Bob’s device has signed his master key, then Alice’s device can trust Bob’s master key, and she can sign it with her user-signing key.
Users upload their cross-signing keys to the server using POST
/_matrix/client/v3/keys/device_signing/upload. When Alice uploads
new cross-signing keys, her user ID will appear in the changed
property of the device_lists field of the /sync of response of all
users who share an encrypted room with her. When Bob sees Alice’s user
ID in his /sync, he will call POST /_matrix/client/v3/keys/query
to retrieve Alice’s device and cross-signing keys.
If Alice has a device and wishes to send an encrypted message to Bob, she can trust Bob’s device if:
- Alice’s device is using a master key that has signed her user-signing key,
- Alice’s user-signing key has signed Bob’s master key,
- Bob’s master key has signed Bob’s self-signing key, and
- Bob’s self-signing key has signed Bob’s device key.
The following diagram illustrates how keys are signed:
+------------------+ .................. +----------------+
| +--------------+ | .................. : | +------------+ |
| | 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 | ...| | |
| | +---------+ -+ +---------+ -+ | |
| | | ... | | ... | | |
| +------+ | | +----+ |
+----------------+ +--------------+
In the diagram, boxes represent keys and lines represent signatures with the arrows pointing from the signing key to the key being signed. Dotted boxes and lines represent keys and signatures that are only visible to the user who created them.
The following diagram illustrates Alice’s view, hiding the keys and signatures that she cannot see:
+------------------+ +----------------+ +----------------+
| +--------------+ | | | | +------------+ |
| | v v | v v v | |
| | +-----------+ | +-----------+ | |
| | | Alice MSK | | | Bob MSK | | |
| | +-----------+ | +-----------+ | |
| | | | | | | |
| | +--+ +--+ | +--+ | |
| | v v | v | |
| | +-----------+ +-----------+ | +-----------+ | |
| | | Alice SSK | | Alice USK | | | Bob SSK | | |
| | +-----------+ +-----------+ | +-----------+ | |
| | | ... | | | | ... | | |
| | V V +--------+ V V | |
| | +---------+ -+ +---------+ -+ | |
| | | Devices | ...| | Devices | ...| | |
| | +---------+ -+ +---------+ -+ | |
| | | ... | | ... | | |
| +------+ | | +----+ |
+----------------+ +--------------+
Verification methods can be used to verify a
user’s master key by using the master public key, encoded using unpadded
base64, as the device ID, and treating it as a normal device. For
example, if Alice and Bob verify each other using SAS, Alice’s
m.key.verification.mac message to Bob may include
"ed25519:alices+master+public+key": "alices+master+public+key" in the
mac property. Servers therefore must ensure that device IDs will not
collide with cross-signing public keys.
The cross-signing private keys can be stored on the server or shared with other
devices using the Secrets module. When doing so, the master,
user-signing, and self-signing keys are identified using the names
m.cross_signing.master, m.cross_signing.user_signing, and
m.cross_signing.self_signing, respectively, and the keys are base64-encoded
before being encrypted.
Key and signature security
A user’s master key could allow an attacker to impersonate that user to other users, or other users to that user. Thus clients must ensure that the private part of the master key is treated securely. If clients do not have a secure means of storing the master key (such as a secret storage system provided by the operating system), then clients must not store the private part.
If a user’s client sees that any other user has changed their master key, that client must notify the user about the change before allowing communication between the users to continue.
A user’s user-signing and self-signing keys are intended to be easily replaceable if they are compromised by re-issuing a new key signed by the user’s master key and possibly by re-verifying devices or users. However, doing so relies on the user being able to notice when their keys have been compromised, and it involves extra work for the user, and so although clients do not have to treat the private parts as sensitively as the master key, clients should still make efforts to store the private part securely, or not store it at all. Clients will need to balance the security of the keys with the usability of signing users and devices when performing key verification.
To avoid leaking of social graphs, servers will only allow users to see:
- signatures made by the user’s own master, self-signing or user-signing keys,
- signatures made by the user’s own devices about their own master key,
- signatures made by other users’ self-signing keys about their respective devices,
- signatures made by other users’ master keys about their respective self-signing key, or
- signatures made by other users’ devices about their respective master keys.
Users will not be able to see signatures made by other users’ user-signing keys.
POST
/_matrix/client/v3/keys/device_signing/upload
Added in v1.1
Publishes cross-signing keys for the user.
This API endpoint uses the User-Interactive Authentication API.
v1.1| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request body
| Name | Type | Description |
|---|---|---|
auth |
Authentication Data |
Additional authentication information for the user-interactive authentication API. |
master_key |
CrossSigningKey |
Optional. The user's master key. |
self_signing_key |
CrossSigningKey |
Optional. The user's self-signing key. Must be signed by the accompanying master key, or by the user's most recently uploaded master key if no master key is included in the request. |
user_signing_key |
CrossSigningKey |
Optional. The user's user-signing key. Must be signed by the accompanying master key, or by the user's most recently uploaded master key if no master key is included in the request. |
| Name | Type | Description |
|---|---|---|
session |
string |
The value of the session key given by the homeserver. |
type |
string |
The authentication type that the client is attempting to complete.
May be omitted if session is given, and the client is reissuing a
request which it believes has been completed out-of-band (for example,
via the fallback mechanism). |
| Name | Type | Description |
|---|---|---|
keys |
object |
Required: The public key. The object must have exactly one property, whose name is
in the form <algorithm>:<unpadded_base64_public_key>, and whose value
is the unpadded base64 public key. |
signatures |
Signatures |
Signatures of the key, calculated using the process described at Signing JSON. Optional for the master key. Other keys must be signed by the user's master key. |
usage |
[string] |
Required: What the key is used for. |
user_id |
string |
Required: The ID of the user the key belongs to. |
Request body example
{
"auth": {
"example_credential": "verypoorsharedsecret",
"session": "xxxxx",
"type": "example.type.foo"
},
"master_key": {
"keys": {
"ed25519:alice+base64+public+key": "alice+base64+public+key",
"ed25519:base64+master+public+key": "base64+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:alice+base64+master+key": "signature+of+key"
}
},
"usage": [
"master"
],
"user_id": "@alice:example.com"
},
"self_signing_key": {
"keys": {
"ed25519:alice+base64+public+key": "alice+base64+public+key",
"ed25519:base64+self+signing+public+key": "base64+self+signing+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:alice+base64+master+key": "signature+of+key",
"ed25519:base64+master+public+key": "signature+of+self+signing+key"
}
},
"usage": [
"self_signing"
],
"user_id": "@alice:example.com"
},
"user_signing_key": {
"keys": {
"ed25519:alice+base64+public+key": "alice+base64+public+key",
"ed25519:base64+user+signing+public+key": "base64+user+signing+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:alice+base64+master+key": "signature+of+key",
"ed25519:base64+master+public+key": "signature+of+user+signing+key"
}
},
"usage": [
"user_signing"
],
"user_id": "@alice:example.com"
}
}
Responses
| Status | Description |
|---|---|
200 |
The provided keys were successfully uploaded. |
400 |
The input was invalid in some way. This can include one of the following error codes:
|
403 |
The public key of one of the keys is the same as one of the user's device IDs, or the request is not authorized for any other reason. |
200 response
{}
400 response
{
"errcode": "M_INVALID_SIGNATURE",
"error": "Invalid signature"
}
403 response
{
"errcode": "M_FORBIDDEN",
"error": "Key ID in use"
}
POST
/_matrix/client/v3/keys/signatures/upload
Added in v1.1
Publishes cross-signing signatures for the user. The request body is a
map from user ID to key ID to signed JSON object.
v1.1| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request body
Request body example
{
"@alice:example.com": {
"HIJKLMN": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha256",
"m.megolm.v1.aes-sha"
],
"device_id": "HIJKLMN",
"keys": {
"curve25519:HIJKLMN": "base64+curve25519+key",
"ed25519:HIJKLMN": "base64+ed25519+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:base64+self+signing+public+key": "base64+signature+of+HIJKLMN"
}
},
"user_id": "@alice:example.com"
},
"base64+master+public+key": {
"keys": {
"ed25519:base64+master+public+key": "base64+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:HIJKLMN": "base64+signature+of+master+key"
}
},
"usage": [
"master"
],
"user_id": "@alice:example.com"
}
},
"@bob:example.com": {
"bobs+base64+master+public+key": {
"keys": {
"ed25519:bobs+base64+master+public+key": "bobs+base64+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:base64+user+signing+public+key": "base64+signature+of+bobs+master+key"
}
},
"usage": [
"master"
],
"user_id": "@bob:example.com"
}
}
}
Responses
| Status | Description |
|---|---|
200 |
The provided signatures were processed. |
200 response
| Name | Type | Description |
|---|---|---|
failures |
object |
A map from user ID to key ID to an error for any signatures
that failed. If a signature was invalid, the errcode will
be set to M_INVALID_SIGNATURE. |
{
"failures": {
"@alice:example.com": {
"HIJKLMN": {
"errcode": "M_INVALID_SIGNATURE",
"error": "Invalid signature"
}
}
}
}
QR codes
[Added in v1.1]
Verifying by QR codes provides a quick way to verify when one of the parties has a device capable of scanning a QR code. The QR code encodes both parties' master signing keys as well as a random shared secret that is used to allow bi-directional verification from a single scan.
To advertise the ability to show a QR code, clients use the names
m.qr_code.show.v1 and m.reciprocate.v1 in the methods fields of the
m.key.verification.request and m.key.verification.ready events. To
advertise the ability to scan a QR code, clients use the names
m.qr_code.scan.v1 and m.reciprocate.v1 in the methods fields of the
m.key.verification.request and m.key.verification.ready events.
Clients that support both showing and scanning QR codes would advertise
m.qr_code.show.v1, m.qr_code.scan.v1, and m.reciprocate.v1 as
methods.
The process between Alice and Bob verifying each other would be:
-
Alice and Bob meet in person, and want to verify each other’s keys.
-
Alice and Bob begin a key verification using the key verification framework as described above.
-
Alice’s client displays a QR code that Bob is able to scan if Bob’s client indicated the ability to scan, an option to scan Bob’s QR code if her client is able to scan. Bob’s client prompts displays a QR code that Alice can scan if Alice’s client indicated the ability to scan, and an option to scan Alice’s QR code if his client is able to scan. The format for the QR code is described below. Other options, like starting SAS Emoji verification, can be presented alongside the QR code if the devices have appropriate support.
-
Alice scans Bob’s QR code.
-
Alice’s device ensures that the keys encoded in the QR code match the expected values for the keys. If not, Alice’s device displays an error message indicating that the code is incorrect, and sends a
m.key.verification.cancelmessage to Bob’s device.Otherwise, at this point:
- Alice’s device has now verified Bob’s key, and
- Alice’s device knows that Bob has the correct key for her.
Thus for Bob to verify Alice’s key, Alice needs to tell Bob that he has the right key.
-
Alice’s device displays a message saying that the verification was successful because the QR code’s keys will have matched the keys expected for Bob. Bob’s device hasn’t had a chance to verify Alice’s keys yet so wouldn’t show the same message. Bob will know that he has the right key for Alice because Alice’s device will have shown this message, as otherwise the verification would be cancelled.
-
Alice’s device sends an
m.key.verification.startmessage withmethodset tom.reciprocate.v1to Bob (see below). The message includes the shared secret from the QR code. This signals to Bob’s device that Alice has scanned Bob’s QR code.This message is merely a signal for Bob’s device to proceed to the next step, and is not used for verification purposes.
-
Upon receipt of the
m.key.verification.startmessage, Bob’s device ensures that the shared secret matches.If the shared secret does not match, it should display an error message indicating that an attack was attempted. (This does not affect Alice’s verification of Bob’s keys.)
If the shared secret does match, it asks Bob to confirm that Alice has scanned the QR code.
-
Bob sees Alice’s device confirm that the key matches, and presses the button on his device to indicate that Alice’s key is verified.
Bob’s verification of Alice’s key hinges on Alice telling Bob the result of her scan. Since the QR code includes what Bob thinks Alice’s key is, Alice’s device can check whether Bob has the right key for her. Alice has no motivation to lie about the result, as getting Bob to trust an incorrect key would only affect communications between herself and Bob. Thus Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice’s key, under the assumption that this communication is done over a trusted medium (such as in-person).
-
Both devices send an
m.key.verification.donemessage.
QR code format
The QR codes to be displayed and scanned using this format will encode binary strings in the general form:
- the ASCII string
MATRIX - one byte indicating the QR code version (must be
0x02) - one byte indicating the QR code verification mode. Should be one of the
following values:
0x00verifying another user with cross-signing0x01self-verifying in which the current device does trust the master key0x02self-verifying in which the current device does not yet trust the master key
- the event ID or
transaction_idof the associated verification request event, encoded as:- two bytes in network byte order (big-endian) indicating the length in bytes of the ID as a UTF-8 string
- the ID as a UTF-8 string
- the first key, as 32 bytes. The key to use depends on the mode field:
- if
0x00or0x01, then the current user’s own master cross-signing public key - if
0x02, then the current device’s device key
- if
- the second key, as 32 bytes. The key to use depends on the mode field:
- if
0x00, then what the device thinks the other user’s master cross-signing key is - if
0x01, then what the device thinks the other device’s device key is - if
0x02, then what the device thinks the user’s master cross-signing key is
- if
- a random shared secret, as a byte string. It is suggested to use a secret that is about 8 bytes long. Note: as we do not share the length of the secret, and it is not a fixed size, clients will just use the remainder of binary string as the shared secret.
For example, if Alice displays a QR code encoding the following binary string:
"MATRIX" |ver|mode| len | event ID
4D 41 54 52 49 58 02 00 00 2D 21 41 42 43 44 ...
| user's cross-signing key | other user's cross-signing key | shared secret
00 01 02 03 04 05 06 07 ... 10 11 12 13 14 15 16 17 ... 20 21 22 23 24 25 26 27
this indicates that Alice is verifying another user (say Bob), in response to
the request from event “$ABCD…”, her cross-signing key is
0001020304050607... (which is “AAECAwQFBg…” in base64), she thinks that
Bob’s cross-signing key is 1011121314151617... (which is “EBESExQVFh…” in
base64), and the shared secret is 2021222324252627 (which is “ICEiIyQlJic” in
base64).
Verification messages specific to QR codes
m.key.verification.start$m.reciprocate.v1
Begins a key verification process using the m.reciprocate.v1 method, after
scanning a QR code.
m.key.verification.start$m.reciprocate.v1
m.reciprocate.v1 method, after
scanning a QR code.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
from_device |
string |
Required: The device ID which is initiating the process. |
m.relates_to |
VerificationRelatesTo |
Required when sent as an in-room message. Indicates the
m.key.verification.request that this message is related to. Note that for
encrypted messages, this property should be in the unencrypted portion of the
event. |
method |
enum |
Required: The verification method to use. One of: |
secret |
string |
Required: The shared secret from the QR code, encoded using unpadded base64. |
transaction_id |
string |
Required when sent as a to-device message. An opaque identifier for
the verification process. Must be unique with respect to the devices
involved. Must be the same as the transaction_id given in the
m.key.verification.request if this process is originating from a
request. |
| Name | Type | Description |
|---|---|---|
event_id |
string |
The event ID of the m.key.verification.request that this message is
related to. |
rel_type |
enum |
The relationship type. One of: |
Examples
Sharing keys between devices
If Bob has an encrypted conversation with Alice on his computer, and then logs in through his phone for the first time, he may want to have access to the previously exchanged messages. To address this issue, several methods are provided to allow users to transfer keys from one device to another.
Key requests
When a device is missing keys to decrypt messages, it can request the
keys by sending m.room_key_request to-device messages to other
devices with action set to request.
If a device wishes to share the keys with that device, it can forward
the keys to the first device by sending an encrypted
m.forwarded_room_key to-device message. The first device should
then send an m.room_key_request to-device message with action
set to request_cancellation to the other devices that it had
originally sent the key request to; a device that receives a
request_cancellation should disregard any previously-received
request message with the same request_id and requesting_device_id.
If a device does not wish to share keys with that device, it can indicate this by sending an m.room_key.withheld to-device message, as described in Reporting that decryption keys are withheld.
Server-side key backups
Devices may upload encrypted copies of keys to the server. When a device tries to read a message that it does not have keys for, it may request the key from the server and decrypt it. Backups are per-user, and users may replace backups with new backups.
In contrast with Key requests, Server-side key backups do not require another device to be online from which to request keys. However, as the session keys are stored on the server encrypted, it requires users to enter a decryption key to decrypt the session keys.
To create a backup, a client will call POST
/_matrix/client/v3/room_keys/version and define how the keys are to
be encrypted through the backup’s auth_data; other clients can
discover the backup by calling GET
/_matrix/client/v3/room_keys/version. Keys are encrypted according
to the backup’s auth_data and added to the backup by calling PUT
/_matrix/client/v3/room_keys/keys or one of its variants, and can
be retrieved by calling GET /_matrix/client/v3/room_keys/keys or
one of its variants. Keys can only be written to the most recently
created version of the backup. Backups can also be deleted using DELETE
/_matrix/client/v3/room_keys/version/{version}, or individual keys
can be deleted using DELETE /_matrix/client/v3/room_keys/keys or
one of its variants.
Clients must only store keys in backups after they have ensured that the
auth_data is trusted, either by checking the signatures on it, or by
deriving the public key from a private key that it obtained from a
trusted source.
When a client uploads a key for a session that the server already has a key for, the server will choose to either keep the existing key or replace it with the new key based on the key metadata as follows:
- if the keys have different values for
is_verified, then it will keep the key that hasis_verifiedset totrue; - if they have the same values for
is_verified, then it will keep the key with a lowerfirst_message_index; - and finally, if
is_verifiedandfirst_message_indexare equal, then it will keep the key with a lowerforwarded_count.
Recovery key
If the recovery key (the private half of the backup encryption key) is presented to the user to save, it is presented as a string constructed as follows:
- The 256-bit curve25519 private key is prepended by the bytes
0x8Band0x01 - All the bytes in the string above, including the two header bytes, are XORed together to form a parity byte. This parity byte is appended to the byte string.
- The byte string is encoded using base58, using the same mapping as
is used for Bitcoin
addresses,
that is, using the alphabet
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz. - A space should be added after every 4th character.
When reading in a recovery key, clients must disregard whitespace, and perform the reverse of steps 1 through 3.
The recovery key can also be stored on the server or shared with other devices
using the Secrets module. When doing so, it is identified using the
name m.megolm_backup.v1, and the key is base64-encoded before being
encrypted.
Backup algorithm: m.megolm_backup.v1.curve25519-aes-sha2
When a backup is created with the algorithm set to
m.megolm_backup.v1.curve25519-aes-sha2, the auth_data should have
the following format:
AuthData
The format of the auth_data when a key backup is created with the
algorithm set to m.megolm_backup.v1.curve25519-aes-sha2.
AuthData
auth_data when a key backup is created with the
algorithm set to m.megolm_backup.v1.curve25519-aes-sha2.| Name | Type | Description |
|---|---|---|
public_key |
string |
Required: The curve25519 public key used to encrypt the backups, encoded in unpadded base64. |
signatures |
object |
Signatures of the auth_data, as Signed JSON |
Examples
{
"public_key": "abcdefg",
"signatures": {
"something": {
"ed25519:something": "hijklmnop"
}
}
}
The session_data field in the backups is constructed as follows:
-
Encode the session key to be backed up as a JSON object using the
SessionDataformat defined below. -
Generate an ephemeral curve25519 key, and perform an ECDH with the ephemeral key and the backup’s public key to generate a shared secret. The public half of the ephemeral key, encoded using unpadded base64, becomes the
ephemeralproperty of thesession_data. -
Using the shared secret, generate 80 bytes by performing an HKDF using SHA-256 as the hash, with a salt of 32 bytes of 0, and with the empty string as the info. The first 32 bytes are used as the AES key, the next 32 bytes are used as the MAC key, and the last 16 bytes are used as the AES initialization vector.
-
Stringify the JSON object, and encrypt it using AES-CBC-256 with PKCS#7 padding. This encrypted data, encoded using unpadded base64, becomes the
ciphertextproperty of thesession_data. -
Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above. The first 8 bytes of the resulting MAC are base64-encoded, and become the
macproperty of thesession_data.
SessionData
The format of a backed-up session key, prior to encryption, when using the
m.megolm_backup.v1.curve25519-aes-sha2 algorithm.
SessionData
m.megolm_backup.v1.curve25519-aes-sha2 algorithm.| Name | Type | Description |
|---|---|---|
algorithm |
string |
Required: The end-to-end message encryption algorithm that the key is for. Must be m.megolm.v1.aes-sha2. |
forwarding_curve25519_key_chain |
[string] |
Required: Chain of Curve25519 keys through which this session was forwarded, via m.forwarded_room_key events. |
sender_claimed_keys |
object |
Required: A map from algorithm name (ed25519) to the Ed25519 signing key of the sending device. |
sender_key |
string |
Required: Unpadded base64-encoded device Curve25519 key. |
session_key |
string |
Required: Unpadded base64-encoded session key in session-export format. |
Examples
{
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"sender_claimed_keys": {
"ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y"
},
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
}
GET
/_matrix/client/v3/room_keys/keys
Added in v1.1
Retrieve the keys from the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to retrieve the keys. |
Responses
| Status | Description |
|---|---|
200 |
The key data. If no keys are found, then an object with an empty
rooms property will be returned ({"rooms": {}}). |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
rooms |
object |
Required: A map of room IDs to room key backup data. |
{
"rooms": {
"!room:example.org": {
"sessions": {
"sessionid1": {
"first_message_index": 1,
"forwarded_count": 0,
"is_verified": true,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
}
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version."
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
PUT
/_matrix/client/v3/room_keys/keys
Added in v1.1
Store several keys in the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup in which to store the keys. Must be the current backup. |
Request body
| Name | Type | Description |
|---|---|---|
rooms |
object |
Required: A map of room IDs to room key backup data. |
Request body example
{
"rooms": {
"!room:example.org": {
"sessions": {
"sessionid1": {
"first_message_index": 1,
"forwarded_count": 0,
"is_verified": true,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
}
}
Responses
| Status | Description |
|---|---|
200 |
The update succeeded |
403 |
The version specified does not match the current backup version.
The current version will be included in the current_version
field. |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
403 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"current_version": "42",
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version."
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
DELETE
/_matrix/client/v3/room_keys/keys
Added in v1.1
Delete the keys from the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to delete the key |
Responses
| Status | Description |
|---|---|
200 |
The update succeeded |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
GET
/_matrix/client/v3/room_keys/keys/{roomId}
Added in v1.1
Retrieve the keys from the backup for a given room.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the requested key is for. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to retrieve the key. |
Responses
| Status | Description |
|---|---|
200 |
The key data. If no keys are found, then an object with an empty
sessions property will be returned ({"sessions": {}}). |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
sessions |
object |
Required: A map of session IDs to key data. |
{
"sessions": {
"sessionid1": {
"first_message_index": 1,
"forwarded_count": 0,
"is_verified": true,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
PUT
/_matrix/client/v3/room_keys/keys/{roomId}
Added in v1.1
Store several keys in the backup for a given room.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the keys are for. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup in which to store the keys. Must be the current backup. |
Request body
| Name | Type | Description |
|---|---|---|
sessions |
object |
Required: A map of session IDs to key data. |
Request body example
{
"sessions": {
"sessionid1": {
"first_message_index": 1,
"forwarded_count": 0,
"is_verified": true,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
Responses
| Status | Description |
|---|---|
200 |
The update succeeded |
403 |
The version specified does not match the current backup version.
The current version will be included in the current_version
field. |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
403 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"current_version": "42",
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version."
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
DELETE
/_matrix/client/v3/room_keys/keys/{roomId}
Added in v1.1
Delete the keys from the backup for a given room.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the specified key is for. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to delete the key. |
Responses
| Status | Description |
|---|---|
200 |
The update succeeded |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
GET
/_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}
Added in v1.1
Retrieve a key from the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the requested key is for. |
sessionId |
string |
Required: The ID of the megolm session whose key is requested. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to retrieve the key. |
Responses
| Status | Description |
|---|---|
200 |
The key data |
404 |
The key or backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
first_message_index |
integer |
Required: The index of the first message in the session that the key can decrypt. |
forwarded_count |
integer |
Required: The number of times this key has been forwarded via key-sharing between devices. |
is_verified |
boolean |
Required: Whether the device backing up the key verified the device that the key is from. |
session_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
{
"first_message_index": 1,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Key not found."
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
PUT
/_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}
Added in v1.1
Store a key in the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the key is for. |
sessionId |
string |
Required: The ID of the megolm session that the key is for. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup in which to store the key. Must be the current backup. |
Request body
| Name | Type | Description |
|---|---|---|
first_message_index |
integer |
Required: The index of the first message in the session that the key can decrypt. |
forwarded_count |
integer |
Required: The number of times this key has been forwarded via key-sharing between devices. |
is_verified |
boolean |
Required: Whether the device backing up the key verified the device that the key is from. |
session_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
Request body example
{
"first_message_index": 1,
"session_data": {
"ciphertext": "base64+ciphertext+of+JSON+data",
"ephemeral": "base64+ephemeral+key",
"mac": "base64+mac+of+ciphertext"
}
}
Responses
| Status | Description |
|---|---|
200 |
The update succeeded. |
403 |
The version specified does not match the current backup version.
The current version will be included in the current_version
field. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
403 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"current_version": "42",
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version."
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
DELETE
/_matrix/client/v3/room_keys/keys/{roomId}/{sessionId}
Added in v1.1
Delete a key from the backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
roomId |
string |
Required: The ID of the room that the specified key is for. |
sessionId |
string |
Required: The ID of the megolm session whose key is to be deleted. |
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup from which to delete the key |
Responses
| Status | Description |
|---|---|
200 |
The update succeeded |
404 |
The backup was not found. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
count |
integer |
Required: The number of keys stored in the backup |
etag |
string |
Required: The new etag value representing stored keys in the backup.
See GET /room_keys/version/{version} for more details. |
{
"count": 10,
"etag": "abcdefg"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
GET
/_matrix/client/v3/room_keys/version
Added in v1.1
Get information about the latest backup version.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
No request parameters or request body.
Responses
| Status | Description |
|---|---|
200 |
The information about the backup. |
404 |
No backup exists. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The algorithm used for storing backups. One of: |
auth_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
count |
integer |
Required: The number of keys stored in the backup. |
etag |
string |
Required: An opaque string representing stored keys in the backup.
Clients can compare it with the etag value they received
in the request of their last key storage request. If not
equal, another client has modified the backup. |
version |
string |
Required: The backup version. |
{
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
},
"count": 42,
"etag": "anopaquestring",
"version": "1"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "No current backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
POST
/_matrix/client/v3/room_keys/version
Added in v1.1
Creates a new backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request body
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The algorithm used for storing backups. One of: |
auth_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
Request body example
{
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
}
}
Responses
| Status | Description |
|---|---|
200 |
The version id of the new backup. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup version. This is an opaque string. |
{
"version": "1"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
GET
/_matrix/client/v3/room_keys/version/{version}
Added in v1.1
Get information about an existing backup.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup version to get, as returned in the version parameter
of the response in
POST /_matrix/client/v3/room_keys/version
or this endpoint. |
Responses
| Status | Description |
|---|---|
200 |
The information about the requested backup. |
404 |
The backup specified does not exist. |
429 |
This request was rate-limited. |
200 response
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The algorithm used for storing backups. One of: |
auth_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
count |
integer |
Required: The number of keys stored in the backup. |
etag |
string |
Required: An opaque string representing stored keys in the backup.
Clients can compare it with the etag value they received
in the request of their last key storage request. If not
equal, another client has modified the backup. |
version |
string |
Required: The backup version. |
{
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
},
"count": 42,
"etag": "anopaquestring",
"version": "1"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
PUT
/_matrix/client/v3/room_keys/version/{version}
Added in v1.1
Update information about an existing backup. Only auth_data can be modified.
v1.1auth_data can be modified.| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup version to update, as returned in the version
parameter in the response of
POST /_matrix/client/v3/room_keys/version
or GET /_matrix/client/v3/room_keys/version/{version}. |
Request body
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The algorithm used for storing backups. Must be the same as
the algorithm currently used by the backup. One of: |
auth_data |
object |
Required: Algorithm-dependent data. See the documentation for the backup algorithms in Server-side key backups for more information on the expected format of the data. |
version |
string |
The backup version. If present, must be the same as the version in the path parameter. |
Request body example
{
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
},
"version": "1"
}
Responses
| Status | Description |
|---|---|
200 |
The update succeeded. |
400 |
A parameter was incorrect. For example, the algorithm does not
match the current backup algorithm, or the version in the body
does not match the version in the path. |
404 |
The backup specified does not exist. |
429 |
This request was rate-limited. |
200 response
{}
400 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_INVALID_PARAM",
"error": "Algorithm does not match"
}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
DELETE
/_matrix/client/v3/room_keys/version/{version}
Added in v1.1
Delete an existing key backup. Both the information about the backup,
as well as all key data related to the backup will be deleted.
v1.1| Rate-limited: | Yes |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
version |
string |
Required: The backup version to delete, as returned in the version
parameter in the response of
POST /_matrix/client/v3/room_keys/version
or GET /_matrix/client/v3/room_keys/version/{version}. |
Responses
| Status | Description |
|---|---|
200 |
The delete succeeded, or the specified backup was previously deleted. |
404 |
The backup specified does not exist. If the backup was previously deleted, the call should succeed rather than returning an error. |
429 |
This request was rate-limited. |
200 response
{}
404 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: An error code. |
error |
string |
A human-readable error message. |
{
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
429 response
| Name | Type | Description |
|---|---|---|
errcode |
string |
Required: The M_LIMIT_EXCEEDED error code |
error |
string |
A human-readable error message. |
retry_after_ms |
integer |
The amount of time in milliseconds the client should wait before trying the request again. |
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
Key exports
Keys can be manually exported from one device to an encrypted file, copied to another device, and imported. The file is encrypted using a user-supplied passphrase, and is created as follows:
-
Encode the sessions as a JSON object, formatted as described in Key export format.
-
Generate a 512-bit key from the user-entered passphrase by computing PBKDF2(HMAC-SHA-512, passphrase, S, N, 512), where S is a 128-bit cryptographically-random salt and N is the number of rounds. N should be at least 100,000. The keys K and K’ are set to the first and last 256 bits of this generated key, respectively. K is used as an AES-256 key, and K’ is used as an HMAC-SHA-256 key.
-
Serialize the JSON object as a UTF-8 string, and encrypt it using AES-CTR-256 with the key K generated above, and with a 128-bit cryptographically-random initialization vector, IV, that has bit 63 set to zero. (Setting bit 63 to zero in IV is needed to work around differences in implementations of AES-CTR.)
-
Concatenate the following data:
| Size (bytes) | Description |
|---|---|
| 1 | Export format version, which must be 0x01. |
| 16 | The salt S. |
| 16 | The initialization vector IV. |
| 4 | The number of rounds N, as a big-endian unsigned 32-bit integer. |
| variable | The encrypted JSON object. |
| 32 | The HMAC-SHA-256 of all the above string concatenated together, using K’ as the key. |
-
Base64-encode the string above. Newlines may be added to avoid overly long lines.
-
Prepend the resulting string with
-----BEGIN MEGOLM SESSION DATA-----, with a trailing newline, and append-----END MEGOLM SESSION DATA-----, with a leading and trailing newline.
Key export format
The exported sessions are formatted as a JSON array of SessionData
objects described as follows:
SessionData
The format used to encode a Megolm session key for export.
This is similar to the format before encryption used for the session keys
in Server-side key backups but adds the
room_id and session_id fields.
SessionData
room_id and session_id fields.| Name | Type | Description |
|---|---|---|
algorithm |
string |
Required: The end-to-end message encryption algorithm that the key is for. Must be m.megolm.v1.aes-sha2. |
forwarding_curve25519_key_chain |
[string] |
Required: Chain of Curve25519 keys through which this session was forwarded, via m.forwarded_room_key events. |
room_id |
string |
Required: The room where the session is used. |
sender_claimed_keys |
object |
Required: A map from algorithm name (ed25519) to the Ed25519 signing key of the sending device. |
sender_key |
string |
Required: Unpadded base64-encoded device Curve25519 key. |
session_id |
string |
Required: The Megolm session ID. |
session_key |
string |
Required: Unpadded base64-encoded session key in session-export format. |
Examples
{
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_claimed_keys": {
"ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y"
},
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
}
Messaging Algorithms
Messaging Algorithm Names
Messaging algorithm names use the extensible naming scheme used
throughout this specification. Algorithm names that start with m. are
reserved for algorithms defined by this specification. Implementations
wanting to experiment with new algorithms must be uniquely globally
namespaced following Java’s package naming conventions.
Algorithm names should be short and meaningful, and should list the primitives used by the algorithm so that it is easier to see if the algorithm is using a broken primitive.
A name of m.olm.v1 is too short: it gives no information about the
primitives in use, and is difficult to extend for different primitives.
However a name of
m.olm.v1.ecdh-curve25519-hdkfsha256.hmacsha256.hkdfsha256-aes256-cbc-hmac64sha256
is too long despite giving a more precise description of the algorithm:
it adds to the data transfer overhead and sacrifices clarity for human
readers without adding any useful extra information.
m.olm.v1.curve25519-aes-sha2
The name m.olm.v1.curve25519-aes-sha2 corresponds to version 1 of the
Olm ratchet, as defined by the Olm
specification. This uses:
- Curve25519 for the initial key agreement.
- HKDF-SHA-256 for ratchet key derivation.
- Curve25519 for the root key ratchet.
- HMAC-SHA-256 for the chain key ratchet.
- HKDF-SHA-256, AES-256 in CBC mode, and 8 byte truncated HMAC-SHA-256 for authenticated encryption.
Devices that support Olm must include “m.olm.v1.curve25519-aes-sha2” in their list of supported messaging algorithms, must list a Curve25519 device key, and must publish Curve25519 one-time keys.
An event encrypted using Olm has the following format:
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.olm.v1.curve25519-aes-sha2",
"sender_key": "<sender_curve25519_key>",
"ciphertext": {
"<device_curve25519_key>": {
"type": 0,
"body": "<encrypted_payload_base_64>"
}
}
}
}
ciphertext is a mapping from device Curve25519 key to an encrypted
payload for that device. body is a Base64-encoded Olm message body.
type is an integer indicating the type of the message body: 0 for the
initial pre-key message, 1 for ordinary messages.
Olm sessions will generate messages with a type of 0 until they receive a message. Once a session has decrypted a message it will produce messages with a type of 1.
When a client receives a message with a type of 0 it must first check if it already has a matching session. If it does then it will use that session to try to decrypt the message. If there is no existing session then the client must create a new session and use the new session to decrypt the message. A client must not persist a session or remove one-time keys used by a session until it has successfully decrypted a message using that session.
Messages with type 1 can only be decrypted with an existing session. If there is no matching session, the client must treat this as an invalid message.
The plaintext payload is of the form:
{
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
}
}
The type and content of the plaintext message event are given in the payload.
Other properties are included in order to prevent an attacker from
publishing someone else’s curve25519 keys as their own and subsequently
claiming to have sent messages which they didn’t. sender must
correspond to the user who sent the event, recipient to the local
user, and recipient_keys to the local ed25519 key.
Clients must confirm that the sender_key and the ed25519 field value
under the keys property match the keys returned by /keys/query for
the given user, and must also verify the signature of the keys from the
/keys/query response. Without this check, a client cannot be sure that
the sender device owns the private part of the ed25519 key it claims to
have in the Olm payload. This is crucial when the ed25519 key corresponds
to a verified device.
If a client has multiple sessions established with another device, it should use the session from which it last received and successfully decrypted a message. For these purposes, a session that has not received any messages should use its creation time as the time that it last received a message. A client may expire old sessions by defining a maximum number of olm sessions that it will maintain for each device, and expiring sessions on a Least Recently Used basis. The maximum number of olm sessions maintained per device should be at least 4.
Recovering from undecryptable messages
Occasionally messages may be undecryptable by clients due to a variety of reasons. When this happens to an Olm-encrypted message, the client should assume that the Olm session has become corrupted and create a new one to replace it.
To establish a new session, the client sends an m.dummy to-device event to the other party to notify them of the new session details.
Clients should rate-limit the number of sessions it creates per device that it receives a message from. Clients should not create a new session with another device if it has already created one for that given device in the past 1 hour.
Clients should attempt to mitigate loss of the undecryptable messages.
For example, Megolm sessions that were sent using the old session would
have been lost. The client can attempt to retrieve the lost sessions
through m.room_key_request messages.
Clients should send key requests for unknown sessions to all devices for
the user which used the session rather than just the device_id or
sender_key denoted on the event.
This is due to a deprecation of the fields. See
m.megolm.v1.aes-sha2 for more information.
m.megolm.v1.aes-sha2
[Changed in v1.3]
The name m.megolm.v1.aes-sha2 corresponds to version 1 of the Megolm
ratchet, as defined by the Megolm
specification. This uses:
- HMAC-SHA-256 for the hash ratchet.
- HKDF-SHA-256, AES-256 in CBC mode, and 8 byte truncated HMAC-SHA-256 for authenticated encryption.
- Ed25519 for message authenticity.
Devices that support Megolm must support Olm, and include “m.megolm.v1.aes-sha2” in their list of supported messaging algorithms.
An event encrypted using Megolm has the following format:
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"sender_key": "<sender_curve25519_key>",
"device_id": "<sender_device_id>",
"session_id": "<outbound_group_session_id>",
"ciphertext": "<encrypted_payload_base_64>"
}
}
The encrypted payload can contain any message event. The plaintext is of the form:
{
"type": "<event_type>",
"content": "<event_content>",
"room_id": "<the room_id>"
}
We include the room ID in the payload, because otherwise the homeserver would be able to change the room a message was sent in.
Clients must guard against replay attacks by keeping track of the ratchet indices of Megolm sessions. They should reject messages with a ratchet index that they have already decrypted. Care should be taken in order to avoid false positives, as a client may decrypt the same event twice as part of its normal processing.
Similar to Olm events, clients should confirm that the user who sent the
message corresponds to the user the message was expected to come from.
For room events, this means ensuring the event’s sender, room_id, and
the recorded session_id match a trusted session (eg: the session_id
is already known and validated to the client).
As of v1.3, the sender_key and device_id keys are deprecated. They
SHOULD continue to be sent, however they MUST NOT be used to verify the
message’s source.
Clients MUST NOT store or lookup sessions using the sender_key or device_id.
In a future version of the specification the keys can be removed completely, including for sending new messages.
Removing the fields (eventually) improves privacy and security by masking the device which sent the encrypted message as well as reducing the client’s dependence on untrusted data: a malicious server (or similar attacker) could change these values, and other devices/users can simply lie about them too.
We can remove the fields, particularly the sender_key, because the session_id
is already globally unique, therefore making storage and lookup possible without
the need for added context from the sender_key or device_id.
Removing the dependence on the fields gives a privacy gain while also increasing the security of messages transmitted over Matrix.
In order to enable end-to-end encryption in a room, clients can send an
m.room.encryption state event specifying m.megolm.v1.aes-sha2 as its
algorithm property.
When creating a Megolm session in a room, clients must share the
corresponding session key using Olm with the intended recipients, so
that they can decrypt future messages encrypted using this session. An
m.room_key event is used to do this. Clients must also handle
m.room_key events sent by other devices in order to decrypt their
messages.
When a client is updating a Megolm session (room key) in its store, the client MUST ensure:
- that the updated session data comes from a trusted source.
- that the new session key has a lower message index than the existing session key.
Protocol definitions
Events
m.room.encryption
Defines how messages sent in this room should be encrypted.
m.room.encryption
| Event type: | State event |
|---|---|
| State key | A zero-length string. |
Content
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The encryption algorithm to be used to encrypt messages sent in this
room. One of: |
rotation_period_ms |
integer |
How long the session should be used before changing it. 604800000
(a week) is the recommended default. |
rotation_period_msgs |
integer |
How many messages should be sent before changing the session. 100 is the
recommended default. |
Examples
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"rotation_period_ms": 604800000,
"rotation_period_msgs": 100
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.encryption",
"unsigned": {
"age": 1234
}
}
m.room.encrypted
This event type is used when sending encrypted events. It can be used either
within a room (in which case it will have all of the Room Event fields), or
as a to-device event.
m.room.encrypted
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The encryption algorithm used to encrypt this event. The
value of this field determines which other properties will be
present. One of: |
ciphertext |
|
Required: The encrypted content of the event. Either the encrypted payload itself, in the case of a Megolm event, or a map from the recipient Curve25519 identity key to ciphertext information, in the case of an Olm event. For more details, see Messaging Algorithms. |
device_id |
string |
The ID of the sending device. Deprecated: This field provides no additional security or privacy benefit
for Megolm messages and must not be read from if the encrypted event is using
Megolm. It should still be included on outgoing messages, however must not be
used to find the corresponding session. See Changed in v1.3:
Previously this field was required for Megolm messages, however given it
offers no additional security or privacy benefit it has been deprecated
for Megolm messages. See m.megolm.v1.aes-sha2 for
more information.
|
sender_key |
string |
The Curve25519 key of the sender. Required (not deprecated) if not using Megolm. Deprecated: This field provides no additional security or privacy benefit
for Megolm messages and must not be read from if the encrypted event is using
Megolm. It should still be included on outgoing messages, however must not be
used to find the corresponding session. See Changed in v1.3:
Previously this field was required, however given it offers no additional
security or privacy benefit it has been deprecated for Megolm messages.
See m.megolm.v1.aes-sha2 for more information.
|
session_id |
string |
The ID of the session used to encrypt the message. Required with Megolm. |
Examples
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "AwgAEnACgAkLmt6qF84IK++J7UDH2Za1YVchHyprqTqsg...",
"device_id": "RJYKSTBOIE",
"sender_key": "IlRMeOPX2e0MurIyfWEucYBRVOEEUMrOHqn/8mLqMjA",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.encrypted",
"unsigned": {
"age": 1234
}
}
{
"content": {
"algorithm": "m.olm.v1.curve25519-aes-sha2",
"ciphertext": {
"7qZcfnBmbEGzxxaWfBjElJuvn7BZx+lSz/SvFrDF/z8": {
"body": "AwogGJJzMhf/S3GQFXAOrCZ3iKyGU5ZScVtjI0KypTYrW...",
"type": 0
}
},
"sender_key": "Szl29ksW/L8yZGWAX+8dY1XyFi+i5wm+DRhTGkbMiwU"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.encrypted",
"unsigned": {
"age": 1234
}
}
m.room_key
This event type is used to exchange keys for end-to-end encryption. Typically
it is encrypted as an m.room.encrypted event, then sent as a to-device event.
m.room_key
m.room.encrypted event, then sent as a to-device event.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The encryption algorithm the key in this event is to be used with. One of: |
room_id |
string |
Required: The room where the key is used. |
session_id |
string |
Required: The ID of the session that the key is for. |
session_key |
string |
Required: The key to be exchanged. |
Examples
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
},
"type": "m.room_key"
}
m.room_key_request
This event type is used to request keys for end-to-end encryption. It is sent as an
unencrypted to-device event.
m.room_key_request
| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
action |
enum |
Required: One of: |
body |
RequestedKeyInfo |
Information about the requested key. Required when action is
request. |
request_id |
string |
Required: A random string uniquely identifying the request for a key. If the key is requested multiple times, it should be reused. It should also reused in order to cancel a request. |
requesting_device_id |
string |
Required: ID of the device requesting the key. |
| Name | Type | Description |
|---|---|---|
algorithm |
string |
Required: The encryption algorithm the requested key in this event is to be used with. |
room_id |
string |
Required: The room where the key is used. |
sender_key |
string |
The Curve25519 key of the device which initiated the session originally. Deprecated: This field provides no additional security or privacy benefit
and must not be read from. It should still be included on outgoing messages
(if the event for which keys are being requested for also has a Changed in v1.3:
Previously this field was required, however given it offers no additional
security or privacy benefit it has been deprecated. See m.megolm.v1.aes-sha2
for more information.
|
session_id |
string |
Required: The ID of the session that the key is for. |
Examples
{
"content": {
"action": "request_cancellation",
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
}
{
"content": {
"action": "request",
"body": {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
}
m.forwarded_room_key
This event type is used to forward keys for end-to-end encryption. Typically
it is encrypted as an m.room.encrypted event, then sent as a to-device
event.
m.forwarded_room_key
m.room.encrypted event, then sent as a to-device
event.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
algorithm |
string |
Required: The encryption algorithm the key in this event is to be used with. |
forwarding_curve25519_key_chain |
[string] |
Required: Chain of Curve25519 keys. It starts out empty, but each time the key is forwarded to another device, the previous sender in the chain is added to the end of the list. For example, if the key is forwarded from A to B to C, this field is empty between A and B, and contains A’s Curve25519 key between B and C. |
room_id |
string |
Required: The room where the key is used. |
sender_claimed_ed25519_key |
string |
Required: The Ed25519 key of the device which initiated the session originally. It is ‘claimed’ because the receiving device has no way to tell that the original room_key actually came from a device which owns the private part of this key unless they have done device verification. |
sender_key |
string |
Required: The Curve25519 key of the device which initiated the session originally. |
session_id |
string |
Required: The ID of the session that the key is for. |
session_key |
string |
Required: The key to be exchanged. |
withheld |
object |
Indicates that the key cannot be used to decrypt all the messages
from the session because a portion of the session was withheld as
described in Reporting that decryption keys are withheld. This
object must include the code and reason properties from the
m.room_key.withheld message that was received by the sender of
this message. |
Examples
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_claimed_ed25519_key": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
},
"type": "m.forwarded_room_key"
}
m.dummy
This event type is used to indicate new Olm sessions for end-to-end encryption.
Typically it is encrypted as an m.room.encrypted event, then sent as a to-device
event.
The event does not have any content associated with it. The sending client is expected
to send a key share request shortly after this message, causing the receiving client to
process this m.dummy event as the most recent event and using the keyshare request
to set up the session. The keyshare request and m.dummy combination should result
in the original sending client receiving keys over the newly established session.
m.dummy
m.room.encrypted event, then sent as a to-device
event.m.dummy event as the most recent event and using the keyshare request
to set up the session. The keyshare request and m.dummy combination should result
in the original sending client receiving keys over the newly established session.| Event type: | Message event |
|---|
Content
Examples
{
"content": {},
"type": "m.dummy"
}
Key management API
GET
/_matrix/client/v3/keys/changes
Gets a list of users who have updated their device identity keys since a
previous sync token.
The server should include in the results any users who:
- currently share a room with the calling user (ie, both users have
membership state
join); and
- added new device identity keys or removed an existing device with
identity keys, between
from and to.
join); andfrom and to.| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request parameters
| Name | Type | Description |
|---|---|---|
from |
string |
Required: The desired start point of the list. Should be the next_batch field
from a response to an earlier call to /sync. Users who have not
uploaded new device identity keys since this point, nor deleted
existing devices with identity keys since then, will be excluded
from the results. |
to |
string |
Required: The desired end point of the list. Should be the next_batch
field from a recent call to /sync - typically the most recent
such call. This may be used by the server as a hint to check its
caches are up to date. |
Responses
| Status | Description |
|---|---|
200 |
The list of users who updated their devices. |
200 response
| Name | Type | Description |
|---|---|---|
changed |
[string] |
The Matrix User IDs of all users who updated their device identity keys. |
left |
[string] |
The Matrix User IDs of all users who may have left all the end-to-end encrypted rooms they previously shared with the user. |
{
"changed": [
"@alice:example.com",
"@bob:example.org"
],
"left": [
"@clara:example.com",
"@doug:example.org"
]
}
POST
/_matrix/client/v3/keys/claim
Claims one-time keys for use in pre-key messages.
| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request body
| Name | Type | Description |
|---|---|---|
one_time_keys |
object |
Required: The keys to be claimed. A map from user ID, to a map from device ID to algorithm name. |
timeout |
integer |
The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. |
Request body example
{
"one_time_keys": {
"@alice:example.com": {
"JLAFKJWSCS": "signed_curve25519"
}
},
"timeout": 10000
}
Responses
| Status | Description |
|---|---|
200 |
The claimed keys. |
200 response
| Name | Type | Description |
|---|---|---|
failures |
object |
If any remote homeservers could not be reached, they are recorded here. The names of the properties are the names of the unreachable servers. If the homeserver could be reached, but the user or device
was unknown, no failure is recorded. Instead, the corresponding
user or device is missing from the |
one_time_keys |
object |
Required: One-time keys for the queried devices. A map from user ID, to a
map from devices to a map from See the key algorithms section for information on the Key Object format. If necessary, the claimed key might be a fallback key. Fallback keys are re-used by the server until replaced by the device. |
{
"one_time_keys": {
"@alice:example.com": {
"JLAFKJWSCS": {
"signed_curve25519:AAAAHg": {
"key": "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs",
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
}
}
}
}
}
}
}
POST
/_matrix/client/v3/keys/query
Returns the current devices and identity keys for the given users.
| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request body
| Name | Type | Description |
|---|---|---|
device_keys |
object |
Required: The keys to be downloaded. A map from user ID, to a list of device IDs, or to an empty list to indicate all devices for the corresponding user. |
timeout |
integer |
The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. |
token |
string |
If the client is fetching keys as a result of a device update received in a sync request, this should be the ‘since’ token of that sync request, or any later sync token. This allows the server to ensure its response contains the keys advertised by the notification in that sync. |
Request body example
{
"device_keys": {
"@alice:example.com": []
},
"timeout": 10000
}
Responses
| Status | Description |
|---|---|
200 |
The device information |
200 response
| Name | Type | Description |
|---|---|---|
device_keys |
object |
Information on the queried devices. A map from user ID, to a
map from device ID to device information. For each device,
the information returned will be the same as uploaded via
/keys/upload, with the addition of an unsigned
property. |
failures |
object |
If any remote homeservers could not be reached, they are recorded here. The names of the properties are the names of the unreachable servers. If the homeserver could be reached, but the user or device
was unknown, no failure is recorded. Instead, the corresponding
user or device is missing from the |
master_keys |
object |
Information on the master cross-signing keys of the queried users.
A map from user ID, to master key information. For each key, the
information returned will be the same as uploaded via
/keys/device_signing/upload, along with the signatures
uploaded via /keys/signatures/upload that the requesting user
is allowed to see.
Added in |
self_signing_keys |
object |
Information on the self-signing keys of the queried users. A map
from user ID, to self-signing key information. For each key, the
information returned will be the same as uploaded via
/keys/device_signing/upload.
Added in |
user_signing_keys |
object |
Information on the user-signing key of the user making the
request, if they queried their own device information. A map
from user ID, to user-signing key information. The
information returned will be the same as uploaded via
/keys/device_signing/upload. |
{
"device_keys": {
"@alice:example.com": {
"JLAFKJWSCS": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "JLAFKJWSCS",
"keys": {
"curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
},
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
},
"user_id": "@alice:example.com"
}
}
},
"master_keys": {
"@alice:example.com": {
"keys": {
"ed25519:base64+master+public+key": "base64+master+public+key"
},
"usage": [
"master"
],
"user_id": "@alice:example.com"
}
},
"self_signing_keys": {
"@alice:example.com": {
"keys": {
"ed25519:base64+self+signing+public+key": "base64+self+signing+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:base64+master+public+key": "signature+of+self+signing+key"
}
},
"usage": [
"self_signing"
],
"user_id": "@alice:example.com"
}
},
"user_signing_keys": {
"@alice:example.com": {
"keys": {
"ed25519:base64+user+signing+public+key": "base64+user+signing+master+public+key"
},
"signatures": {
"@alice:example.com": {
"ed25519:base64+master+public+key": "signature+of+user+signing+key"
}
},
"usage": [
"user_signing"
],
"user_id": "@alice:example.com"
}
}
}
POST
/_matrix/client/v3/keys/upload
Publishes end-to-end encryption keys for the device.
| Rate-limited: | No |
|---|---|
| Requires authentication: | Yes |
Request
Request body
| Name | Type | Description |
|---|---|---|
device_keys |
DeviceKeys |
Identity keys for the device. May be absent if no new identity keys are required. |
fallback_keys |
OneTimeKeys |
The public key which should be used if the device’s one-time keys
are exhausted. The fallback key is not deleted once used, but should
be replaced when additional one-time keys are being uploaded. The
server will notify the client of the fallback key being used through
There can only be at most one key per algorithm uploaded, and the server will only persist one key per algorithm. When uploading a signed key, an additional May be absent if a new fallback key is not required. Added in |
one_time_keys |
OneTimeKeys |
One-time public keys for “pre-key” messages. The names of
the properties should be in the format
May be absent if no new one-time keys are required. |
| Name | Type | Description |
|---|---|---|
algorithms |
[string] |
Required: The encryption algorithms supported by this device. |
device_id |
string |
Required: The ID of the device these keys belong to. Must match the device ID used when logging in. |
keys |
object |
Required: Public identity keys. The names of the properties should be in the
format <algorithm>:<device_id>. The keys themselves should be
encoded as specified by the key algorithm. |
signatures |
Signatures |
Required: Signatures for the device key object. A map from user ID, to a map from
The signature is calculated using the process described at Signing JSON. |
user_id |
string |
Required: The ID of the user the device belongs to. Must match the user ID used when logging in. |
Request body example
{
"device_keys": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "JLAFKJWSCS",
"keys": {
"curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
},
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
}
},
"user_id": "@alice:example.com"
},
"fallback_keys": {
"curve25519:AAAAAG": "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8",
"signed_curve25519:AAAAGj": {
"fallback": true,
"key": "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs",
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
}
}
}
},
"one_time_keys": {
"curve25519:AAAAAQ": "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8",
"signed_curve25519:AAAAHQ": {
"key": "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw",
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "IQeCEPb9HFk217cU9kw9EOiusC6kMIkoIRnbnfOh5Oc63S1ghgyjShBGpu34blQomoalCyXWyhaaT3MrLZYQAA"
}
}
},
"signed_curve25519:AAAAHg": {
"key": "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs",
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
}
}
}
}
}
Responses
| Status | Description |
|---|---|
200 |
The provided keys were successfully uploaded. |
200 response
| Name | Type | Description |
|---|---|---|
one_time_key_counts |
object |
Required: For each key algorithm, the number of unclaimed one-time keys of that type currently held on the server for this device. If an algorithm is not listed, the count for that algorithm is to be assumed zero. |
{
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20
}
}
Extensions to /sync
This module adds an optional device_lists property to the /sync response,
as specified below. The server need only populate this property for an
incremental /sync (i.e., one where the since parameter was
specified). The client is expected to use /keys/query or
/keys/changes for the equivalent functionality after an initial
sync, as documented in Tracking the device list for a
user.
It also adds a one_time_keys_count property. Note the spelling
difference with the one_time_key_counts property in the
/keys/upload response.
[Added in v1.2]
Finally, a device_unused_fallback_key_types property
is added to list the key algorithms where the device has a fallback key that
has not been used in a /keys/claim
response. When a previously uploaded fallback key’s algorithm is missing
from this list, the device should upload a replacement key alongside any
necessary one-time keys to avoid the fallback key’s further usage. This
property is required for inclusion, though previous versions of the
specification did not have it. In addition to /versions, this can be
a way to identify the server’s support for fallback keys.
| Parameter | Type | Description |
|---|---|---|
| device_lists | DeviceLists | Optional. Information on e2e device updates. Note: only present on an incremental sync. |
| device_one_time_keys_count | {string: integer} | Optional. For each key algorithm, the number of unclaimed one-time keys currently held on the server for this device. If an algorithm is unlisted, the count for that algorithm is assumed to be zero. If this entire parameter is missing, the count for all algorithms is assumed to be zero. |
| device_unused_fallback_key_types | [string] | Required. The unused fallback key algorithms. |
DeviceLists
| Parameter | Type | Description |
|---|---|---|
| changed | [string] | List of users who have updated their device identity or cross-signing keys, or who now share an encrypted room with the client since the previous sync response. |
| left | [string] | List of users with whom we do not share any encrypted rooms anymore since the previous sync response. |
changed in Bob’s
sync only when she updates her devices or cross-signing keys, or when
Alice and Bob now share a room but didn’t share any room previously.
However, for the sake of simpler logic, a server may add Alice to
changed when Alice and Bob share a new room, even if they previously
already shared a room.
Example response:
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"device_lists": {
"changed": [
"@alice:example.com",
],
"left": [
"@bob:example.com",
],
},
"device_one_time_keys_count": {
"curve25519": 10,
"signed_curve25519": 20
}
}
Reporting that decryption keys are withheld
When sending an encrypted event to a room, a client can optionally signal to other devices in that room that it is not sending them the keys needed to decrypt the event. In this way, the receiving client can indicate to the user why it cannot decrypt the event, rather than just showing a generic error message.
In the same way, when one device requests keys from another using Key requests, the device from which the key is being requested may want to tell the requester that it is purposely not sharing the key.
If Alice withholds a megolm session from Bob for some messages in a
room, and then later on decides to allow Bob to decrypt later messages,
she can send Bob the megolm session, ratcheted up to the point at which
she allows Bob to decrypt the messages. If Bob logs into a new device
and uses key sharing to obtain the decryption keys, the new device will
be sent the megolm sessions that have been ratcheted up. Bob’s old
device can include the reason that the session was initially not shared
by including a withheld property in the m.forwarded_room_key message
that is an object with the code and reason properties from the
m.room_key.withheld message.
m.room_key.withheld
This event type is used to indicate that the sender is not sharing room keys
with the recipient. It is sent as a to-device event.
Possible values for code include:
m.blacklisted: the user/device was blacklisted.
m.unverified: the user/device was not verified, and the sender is only
sharing keys with verified users/devices.
m.unauthorised: the user/device is not allowed to have the key. For
example, this could be sent in response to a key request if the user/device
was not in the room when the original message was sent.
m.unavailable: sent in reply to a key request if the device that the
key is requested from does not have the requested key.
m.no_olm: an olm session could not be established.
In most cases, this event refers to a specific room key. The one exception to
this is when the sender is unable to establish an olm session with the
recipient. When this happens, multiple sessions will be affected. In order
to avoid filling the recipient's device mailbox, the sender should only send
one m.room_key.withheld message with no room_id nor session_id
set. If the sender retries and fails to create an olm session again in the
future, it should not send another m.room_key.withheld message with a
code of m.no_olm, unless another olm session was previously
established successfully. In response to receiving an
m.room_key.withheld message with a code of m.no_olm, the
recipient may start an olm session with the sender and send an m.dummy
message to notify the sender of the new olm session. The recipient may
assume that this m.room_key.withheld message applies to all encrypted
room messages sent before it receives the message.
m.room_key.withheld
code include:m.blacklisted: the user/device was blacklisted.m.unverified: the user/device was not verified, and the sender is only
sharing keys with verified users/devices.m.unauthorised: the user/device is not allowed to have the key. For
example, this could be sent in response to a key request if the user/device
was not in the room when the original message was sent.m.unavailable: sent in reply to a key request if the device that the
key is requested from does not have the requested key.m.no_olm: an olm session could not be established.m.room_key.withheld message with no room_id nor session_id
set. If the sender retries and fails to create an olm session again in the
future, it should not send another m.room_key.withheld message with a
code of m.no_olm, unless another olm session was previously
established successfully. In response to receiving an
m.room_key.withheld message with a code of m.no_olm, the
recipient may start an olm session with the sender and send an m.dummy
message to notify the sender of the new olm session. The recipient may
assume that this m.room_key.withheld message applies to all encrypted
room messages sent before it receives the message.| Event type: | Message event |
|---|
Content
| Name | Type | Description |
|---|---|---|
algorithm |
enum |
Required: The encryption algorithm for the key that this event is about. One of: |
code |
enum |
Required: A machine-readable code for why the key was not sent. Codes beginning
with m. are reserved for codes defined in the Matrix
specification. Custom codes must use the Java package naming
convention.One of: |
reason |
string |
A human-readable reason for why the key was not sent. The receiving
client should only use this string if it does not understand the
code. |
room_id |
string |
Required if code is not m.no_olm. The room for the key that
this event is about. |
sender_key |
string |
Required: The unpadded base64-encoded device curve25519 key of the event's sender. |
session_id |
string |
Required of code is not m.no_olm. The session ID of the key
that this event is aboutis for. |
Examples
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"code": "m.unverified",
"reason": "Device not verified",
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"type": "m.room_key.withheld"
}