Merge pull request #2472 from uhoreg/symmetric_ssss
MSC2472: Symmetric SSSS
This commit is contained in:
commit
e264124faa
2 changed files with 133 additions and 39 deletions
|
@ -8,6 +8,12 @@ decryption key for the backups on the server, or cross-signing
|
||||||
([MSC1756](https://github.com/matrix-org/matrix-doc/pull/1756)) can store the
|
([MSC1756](https://github.com/matrix-org/matrix-doc/pull/1756)) can store the
|
||||||
signing keys. This proposal presents a standardized way of storing such data.
|
signing keys. This proposal presents a standardized way of storing such data.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- [MSC2472](https://github.com/matrix-org/matrix-doc/pull/2472) changed the
|
||||||
|
encryption algorithm used from an asymmetric algorithm (Curve25519) to a
|
||||||
|
symmetric algorithm (AES).
|
||||||
|
|
||||||
## Proposal
|
## Proposal
|
||||||
|
|
||||||
Secrets are data that clients need to use and that are sent through or stored
|
Secrets are data that clients need to use and that are sent through or stored
|
||||||
|
@ -32,9 +38,8 @@ Each key has an ID, and the description of the key is stored in the user's
|
||||||
account_data using the event type `m.secret_storage.key.[key ID]`. The contents
|
account_data using the event type `m.secret_storage.key.[key ID]`. The contents
|
||||||
of the account data for the key will include an `algorithm` property, which
|
of the account data for the key will include an `algorithm` property, which
|
||||||
indicates the encryption algorithm used, as well as a `name` property, which is
|
indicates the encryption algorithm used, as well as a `name` property, which is
|
||||||
a human-readable name. The contents will be signed as signed JSON using the
|
a human-readable name. Other properties depend on the encryption algorithm,
|
||||||
user's master cross-signing key. Other properties depend on the encryption
|
and are described below.
|
||||||
algorithm, and are described below.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -43,7 +48,7 @@ A key with ID `abcdefg` is stored in `m.secret_storage.key.abcdefg`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "Some key",
|
"name": "Some key",
|
||||||
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
|
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||||
// ... other properties according to algorithm
|
// ... other properties according to algorithm
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -55,13 +60,6 @@ secrets that the user would expect to be available on all their clients.
|
||||||
Unless the user specifies otherwise, clients will try to use the default key to
|
Unless the user specifies otherwise, clients will try to use the default key to
|
||||||
decrypt secrets.
|
decrypt secrets.
|
||||||
|
|
||||||
Clients MUST ensure that the key is trusted before using it to encrypt secrets.
|
|
||||||
One way to do that is to have the client that creates the key sign the key
|
|
||||||
description (as signed JSON) using the user's master cross-signing key.
|
|
||||||
Another way to do that is to prompt the user to enter the passphrase used to
|
|
||||||
generate the encryption key and ensure that the generated private key
|
|
||||||
corresponds to the public key.
|
|
||||||
|
|
||||||
#### Secret storage
|
#### Secret storage
|
||||||
|
|
||||||
Encrypted data is stored in the user's account_data using the event type
|
Encrypted data is stored in the user's account_data using the event type
|
||||||
|
@ -106,7 +104,7 @@ and the key descriptions for the keys would be:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "Some key",
|
"name": "Some key",
|
||||||
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
|
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||||
// ... other properties according to algorithm
|
// ... other properties according to algorithm
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -116,45 +114,50 @@ and the key descriptions for the keys would be:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "Some other key",
|
"name": "Some other key",
|
||||||
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
|
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||||
// ... other properties according to algorithm
|
// ... other properties according to algorithm
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Encryption algorithms
|
#### Encryption algorithms
|
||||||
|
|
||||||
##### `m.secret_storage.v1.curve25519-aes-sha2`
|
##### `m.secret_storage.v1.aes-hmac-sha2`
|
||||||
|
|
||||||
The public key is stored in the `pubkey` property of the `m.secret_storage.key.[key
|
Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. The data
|
||||||
ID]` account_data as a base64-encoded string.
|
is encrypted and MACed as follows:
|
||||||
|
|
||||||
The data is encrypted and MACed as follows:
|
1. Given the secret storage key, generate 64 bytes by performing an HKDF with
|
||||||
|
SHA-256 as the hash, a salt of 32 bytes of 0, and with the secret name as
|
||||||
|
the info. The first 32 bytes are used as the AES key, and the next 32 bytes
|
||||||
|
are used as the MAC key
|
||||||
|
2. Generate 16 random bytes, set bit 63 to 0 (in order to work around
|
||||||
|
differences in AES-CTR implementations), and use this as the AES
|
||||||
|
initialization vector. This becomes the `iv` property, encoded using base64.
|
||||||
|
3. Encrypt the data using AES-CTR-256 using the AES key generated above. This
|
||||||
|
encrypted data, encoded using base64, becomes the `ciphertext` property.
|
||||||
|
4. Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256
|
||||||
|
using the MAC key generated above. The resulting MAC is base64-encoded and
|
||||||
|
becomes the `mac` property.
|
||||||
|
|
||||||
1. Generate an ephemeral curve25519 key, and perform an ECDH with the ephemeral
|
(We use AES-CTR to match file encryption and key exports.)
|
||||||
key and the public key to generate a shared secret. The public half of the
|
|
||||||
ephemeral key, encoded using base64, becomes the `ephemeral` property.
|
|
||||||
2. 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.
|
|
||||||
4. Encrypt the data using AES-CBC-256 with PKCS#7 padding. This encrypted
|
|
||||||
data, encoded using base64, becomes the `ciphertext` property.
|
|
||||||
5. 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 `mac` property.
|
|
||||||
|
|
||||||
(The key HKDF, AES, and HMAC steps are the same as what are used for encryption
|
For the purposes of allowing clients to check whether a user has correctly
|
||||||
in olm and megolm.)
|
entered the key, clients should:
|
||||||
|
|
||||||
For example, the `m.secret_storage.key.[key ID]` for a key using this algorithm
|
1. encrypt and MAC a message consisting of 32 bytes of 0 as described above,
|
||||||
|
using the empty string as the info parameter to the HKDF in step 1.
|
||||||
|
2. store the `iv` and `mac` in the `m.secret_storage.key.[key ID]`
|
||||||
|
account-data.
|
||||||
|
|
||||||
|
For example, the `m.secret_storage.key.key_id` for a key using this algorithm
|
||||||
could look like:
|
could look like:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "m.default",
|
"name": "m.default",
|
||||||
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
|
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||||
"pubkey": "base64+public+key"
|
"iv": "random+data",
|
||||||
|
"mac": "mac+of+encrypted+zeros"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -164,8 +167,8 @@ and data encrypted using this algorithm could look like this:
|
||||||
{
|
{
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"key_id": {
|
"key_id": {
|
||||||
|
"iv": "16+bytes+base64",
|
||||||
"ciphertext": "base64+encoded+encrypted+data",
|
"ciphertext": "base64+encoded+encrypted+data",
|
||||||
"ephemeral": "base64+ephemeral+key",
|
|
||||||
"mac": "base64+encoded+mac"
|
"mac": "base64+encoded+mac"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +177,7 @@ and data encrypted using this algorithm could look like this:
|
||||||
|
|
||||||
###### Keys
|
###### Keys
|
||||||
|
|
||||||
When a user is given a raw key for `m.secret_storage.v1.curve25519-aes-sha2`,
|
When a user is given a raw key for `m.secret_storage.v1.aes-hmac-sha2`,
|
||||||
it will be encoded as follows (this is the same as what is proposed in MSC1703):
|
it will be encoded as follows (this is the same as what is proposed in MSC1703):
|
||||||
|
|
||||||
* prepend the two bytes 0x8b and 0x01 to the key
|
* prepend the two bytes 0x8b and 0x01 to the key
|
||||||
|
@ -200,7 +203,8 @@ ID]` account-data:
|
||||||
"passphrase": {
|
"passphrase": {
|
||||||
"algorithm": "m.pbkdf2",
|
"algorithm": "m.pbkdf2",
|
||||||
"salt": "MmMsAlty",
|
"salt": "MmMsAlty",
|
||||||
"iterations": 100000
|
"iterations": 100000,
|
||||||
|
"bits": 256
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
@ -209,7 +213,9 @@ ID]` account-data:
|
||||||
**`m.pbkdf2`**
|
**`m.pbkdf2`**
|
||||||
|
|
||||||
The key is generated using PBKDF2 using the salt given in the `salt` parameter,
|
The key is generated using PBKDF2 using the salt given in the `salt` parameter,
|
||||||
and the number of iterations given in the `iterations` parameter.
|
and the number of iterations given in the `iterations` parameter. The key size
|
||||||
|
that is generated is given by the `bits` parameter, or 256 bits if no `bits`
|
||||||
|
parameter is given.
|
||||||
|
|
||||||
### Sharing
|
### Sharing
|
||||||
|
|
||||||
|
|
88
proposals/2472-symmetric-ssss.md
Normal file
88
proposals/2472-symmetric-ssss.md
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# Symmetric SSSS
|
||||||
|
|
||||||
|
[MSC1946 (Secure Secret Storage and
|
||||||
|
Sharing)](https://github.com/matrix-org/matrix-doc/pull/1946) proposed a way of
|
||||||
|
storing encrypted secrets on the server. In the proposal, secrets were
|
||||||
|
encrypted using a Curve25519 key, which was chosen to allow easier migration
|
||||||
|
from key backups that we created before the backup key was stored using it.
|
||||||
|
|
||||||
|
However this does not provide any guarantees that data stored using the
|
||||||
|
proposal came from a trusted source. To remedy this, we propose to change the
|
||||||
|
encryption to use AES with a MAC to ensure that only someone who knows the key
|
||||||
|
is able to store data.
|
||||||
|
|
||||||
|
## Proposal
|
||||||
|
|
||||||
|
* The `m.secret_storage.v1.curve25519-aes-sha2` method proposed in MSC1946 is
|
||||||
|
removed.
|
||||||
|
|
||||||
|
* A new method, `m.secret_storage.v1.aes-hmac-sha2`, is added. With this
|
||||||
|
method, the Secret Storage key may be any size (though 256 bits is
|
||||||
|
recommended), and data is encrypted as follows:
|
||||||
|
|
||||||
|
1. Given the secret storage key, generate 64 bytes by performing an HKDF with
|
||||||
|
SHA-256 as the hash, a salt of 32 bytes of 0, and with the secret name as
|
||||||
|
the info. The first 32 bytes are used as the AES key, and the next 32 bytes
|
||||||
|
are used as the MAC key
|
||||||
|
2. Generate 16 random bytes, set bit 63 to 0 (in order to work around
|
||||||
|
differences in AES-CTR implementations), and use this as the AES
|
||||||
|
initialization vector. This becomes the `iv` property, encoded using base64.
|
||||||
|
3. Encrypt the data using AES-CTR-256 using the AES key generated above. This
|
||||||
|
encrypted data, encoded using base64, becomes the `ciphertext` property.
|
||||||
|
4. Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256
|
||||||
|
using the MAC key generated above. The resulting MAC is base64-encoded and
|
||||||
|
becomes the `mac` property.
|
||||||
|
|
||||||
|
(We use AES-CTR to match file encryption and key exports.)
|
||||||
|
|
||||||
|
If the key Secret Storage key is generated from a passphrase, information
|
||||||
|
about how to generate the key is stored in the `passphrase` property of the
|
||||||
|
key's account-data in a similar manner to what was done with the original
|
||||||
|
`m.secret_storage.v1.curve25519-aes-sha2` method, except that there is an
|
||||||
|
optional `bits` parameter that defaults to 256, and indicates the number of
|
||||||
|
bits that should be generated from PBKDF2 (in other words, the size of the
|
||||||
|
key).
|
||||||
|
|
||||||
|
* For the purposes of allowing clients to check whether a user has correctly
|
||||||
|
entered the key, clients should:
|
||||||
|
|
||||||
|
1. encrypt and MAC a message consisting of 32 bytes of 0 as described above,
|
||||||
|
using the empty string as the info parameter to the HKDF in step 1.
|
||||||
|
2. store the `iv` and `mac` in the `m.secret_storage.key.[key ID]`
|
||||||
|
account-data.
|
||||||
|
|
||||||
|
* The `passthrough` property specified in the "Enconding the recovery key for
|
||||||
|
server-side storage via MSC1946" section of MSC1219 is removed. The primary
|
||||||
|
purpose of that property was to allow easy migration of pre-MSC1946 backups,
|
||||||
|
so that users could reuse the backup recovery key as the Secret Storage key
|
||||||
|
without needing to re-enter the recovery key. However, since we are now
|
||||||
|
using a symmetric encryption algorithm, the client needs to know the key that
|
||||||
|
is used to encrypt, so the purpose of the field cannot be fulfilled.
|
||||||
|
|
||||||
|
* Signing the Secret Storage key with the user's master cross-signing key is no
|
||||||
|
longer required. The key is trusted on the basis of the user entering the
|
||||||
|
key/passphrase.
|
||||||
|
|
||||||
|
|
||||||
|
## Potential issues
|
||||||
|
|
||||||
|
Users who have data stored using the old encryption algorithm will need their
|
||||||
|
data migrated. Clients that support the old algorithm but not the new
|
||||||
|
algorithm will not be able to use the migrated secrets until they are updated
|
||||||
|
with the new algorithms. This should not be a major problem because the only
|
||||||
|
clients that are known to have implemented the old algorithm are Riot
|
||||||
|
Web/Android/iOS, and they have been upgraded to implement the new algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
## Alternatives
|
||||||
|
|
||||||
|
Rather than switching to a symmetric encryption algorithm, we could stay with
|
||||||
|
an asymmetric encryption algorithm, and add on a method to authenticate the
|
||||||
|
data. However, it is much safer to use well-known cryptographic methods rather
|
||||||
|
than trying to invent something new. Since the main reason for using an
|
||||||
|
asymmetric scheme was to ease migration from older key backups without
|
||||||
|
requiring the user to re-enter the key, but this is no longer possible due to
|
||||||
|
the need to authenticate the data using the Secret Storage key, there is no
|
||||||
|
reason to stay with an asymmetric algorithm. It is also better to use
|
||||||
|
cryptographic methods already used in Matrix where possible, rather than
|
||||||
|
introducing something new.
|
Loading…
Add table
Add a link
Reference in a new issue