From 7960290645b2cb011ffff7a419d96cf108a82086 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Sep 2018 10:50:44 +0100 Subject: [PATCH 01/10] Initial MSC for encrypting recovery keys for online megolm backups --- proposals/1686-encrypted-recovery-keys.md | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 proposals/1686-encrypted-recovery-keys.md diff --git a/proposals/1686-encrypted-recovery-keys.md b/proposals/1686-encrypted-recovery-keys.md new file mode 100644 index 00000000000..49bec758b41 --- /dev/null +++ b/proposals/1686-encrypted-recovery-keys.md @@ -0,0 +1,46 @@ +# Proposal for storing an encrypted recovery key on the server to aid recovery of megolm key backups + +## Problem + +[MSC1219](https://github.com/matrix-org/matrix-doc/issues/1219) proposes an API for optionally storing encrypted megolm keys on your homeserver, so if a user loses all their devices, they can still recover their history. The megolm keys are public-key encrypted using a private Curve25519 key that only the end-user has. + +However, there are usability concerns about users having to store their Curve25519 recovery private key in a secure manner. Casual users are likely to be scared away by having to file away a relatively long (e.g. 10 word) generated recovery key. + +## Proposed solution + +Taking inspiration from Apple’s [FileVault 2](https://hal.inria.fr/hal-01460615/document) full-disk encryption, we'd like to give the user the option to encrypt their key with a passphrase and store it on the server (much as Apple store encrypted copies of your FileVault AES key on your hard disk, encrypted by your UNIX account password). This is also similar to storing a passphrased SSH private key on a server for convenience. + +In practice, this means: + * Mandating that clients use a high complexity passphrase to encrypt the recovery key - either by enforcing complexity requirements or by generating it for them (similar to 1Password's password creation UX). + * Symmetrically encrypting the Curve25519 key by N rounds of PBKDF2(key, passphrase) where N=100,000 - similarly to how we encrypt [offline megolm key backups](https://github.com/matrix-org/matrix-doc/issues/1211) today. (TODO: This needs to be fleshed out). + * Storing the encrypted key on the server. + +We propose storing it using the /account_data API: + +```json +PUT /_matrix/client/r0/user/{userId}/account_data/m.recovery_key + +{ + "algorithm": "m.recovery_key.v1.curve25519.pbkdf2", + "data": , + "m.hidden": true, +} +``` + +We propose extending the /account_data API with two conveniences: + * the `m.hidden` event field to indicate to the server that such events should not be included in /sync responses to clients. + * a `GET /_matrix/client/r0/user/{userId}/account_data/m.recovery_key` accessor, symmetrical to the existing PUT method, to access the data when needed. + +We deliberately encrypt the Curve25519 private key rather than deriving it from the passphrase so that if the user chooses not to store their encrypted recovery key on the server, they can benefit from a Curve25519 key generated from a high entropy source of random rather than being needlessly derived from a passphrase of some kind. (TODO: Is this accurate)? + +## Security considerations + +The proposal above is vulnerable to a malicious server admin performing a dictionary attack against the encrypted passphrases stored on their server to access history. (It's worth bearing in mind that the server admin can also always hijack its user's accounts; the thing that stopping them from impersonating their users is E2E device verification.) + +## Possible extensions + +In future, we could consider supporting authenticating users for login based on their encrypted passphrase, meaning that users only have to remember one password for their Matrix account rather than a login password and a history-access passphrase. However, this of course exposes the user's whole E2E history to the risk of dictionary attacks by public attackers (i.e. not just server admins), keysniffer-at-login attacks or clients which are lazy about storing account passwords securely. There's also a risk that because login passwords are much more commonly entered than history passwords, they might encourage users to force a weaker password. It's unclear whether this reduction in security-in-depth is worth the UX benefits of a single master password, so we suggest checking how this proposal goes first (given in general we expect key recovery to happen by cross-verifying devices at login rather than by entering a recovery key or passphrase). + +## See also: + +Notes from discussing this IRL are at https://docs.google.com/document/d/11fF1rbX5eTkrfxXRS8UhpW5sBENOCydYlLWzB8X1IuU/edit \ No newline at end of file From 8c3e04b3b908737ae09d7bcadcf7ac0a607faf40 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Sep 2018 10:54:25 +0100 Subject: [PATCH 02/10] use the right MSC number --- ...encrypted-recovery-keys.md => 1687-encrypted-recovery-keys.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename proposals/{1686-encrypted-recovery-keys.md => 1687-encrypted-recovery-keys.md} (100%) diff --git a/proposals/1686-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md similarity index 100% rename from proposals/1686-encrypted-recovery-keys.md rename to proposals/1687-encrypted-recovery-keys.md From c53aaeed5d99e5e6d1c6a27c0c40c99474896fe3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Oct 2018 16:46:33 +0100 Subject: [PATCH 03/10] Fleshed out proposal for passphrased backups. --- proposals/1687-encrypted-recovery-keys.md | 55 ++++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index 49bec758b41..4fa4f5ff71f 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -6,32 +6,55 @@ However, there are usability concerns about users having to store their Curve25519 recovery private key in a secure manner. Casual users are likely to be scared away by having to file away a relatively long (e.g. 10 word) generated recovery key. +We would like to give the user the option to access their key backup using a passphrase in addition to their recovery key. We can take inspiration from Apple’s [FileVault 2](https://hal.inria.fr/hal-01460615/document) where Apple store encrypted copies of your FileVault AES key on your hard disk, encrypted by your UNIX account password, or a passphrased SSH private key on a server for convenience. + ## Proposed solution -Taking inspiration from Apple’s [FileVault 2](https://hal.inria.fr/hal-01460615/document) full-disk encryption, we'd like to give the user the option to encrypt their key with a passphrase and store it on the server (much as Apple store encrypted copies of your FileVault AES key on your hard disk, encrypted by your UNIX account password). This is also similar to storing a passphrased SSH private key on a server for convenience. +Three solutions are given here (two of which are viable, one included for completeness), varying in the implications of the user changing their passphrase. -In practice, this means: - * Mandating that clients use a high complexity passphrase to encrypt the recovery key - either by enforcing complexity requirements or by generating it for them (similar to 1Password's password creation UX). - * Symmetrically encrypting the Curve25519 key by N rounds of PBKDF2(key, passphrase) where N=100,000 - similarly to how we encrypt [offline megolm key backups](https://github.com/matrix-org/matrix-doc/issues/1211) today. (TODO: This needs to be fleshed out). - * Storing the encrypted key on the server. +### Recovery Key -We propose storing it using the /account_data API: +In all options below, the process for generating a recovery key from a byte string, b is as follows: + * Prepend the two bytes 0x8B, 0x01 to the byte string b + * Compute a parity bit by XORing all bytes of the resulting string (ie. prefix + `byte string`) + * Append the parity byte to the prefix + b + * base58 encode the resulting byte string with alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'. + * Format the resulting ASCII string into groups of 4 characters separated by spaces. -```json -PUT /_matrix/client/r0/user/{userId}/account_data/m.recovery_key +### Option 1 +The user provides a passphrase, P. The client generates the backup encryption private key, K-1 by running PBKDF on this passphrase. The PBKDF parameters are stored in an object in the auth_data of the key backup under the 'private_key' key: + +```json { - "algorithm": "m.recovery_key.v1.curve25519.pbkdf2", - "data": , - "m.hidden": true, + "private_key": { + salt: "MmMsAlty", + rounds: 100000 + } } ``` -We propose extending the /account_data API with two conveniences: - * the `m.hidden` event field to indicate to the server that such events should not be included in /sync responses to clients. - * a `GET /_matrix/client/r0/user/{userId}/account_data/m.recovery_key` accessor, symmetrical to the existing PUT method, to access the data when needed. +The backup public encryption key, K, is determined by running the curve25519 function on K-1 with basepoint {9}. The recovery key is then generated by encoding K-1 as above. + +To change the passphrase, a client creates a completely new backup version, performing the steps above with the new passphrase. The client then re-encrypts all sessions keys and uploads them to the new backup. The user will always get a new recovery key whenever they change their passphrase. + +In this option, the recovery key is generated directly from the passphrase using PBKDF. This means the ciphertext of the backed up keys is more vulnerable to dictionary attacks. We could mitigate this by randomly generating the backup recovery key, encrypting it somehow with the PBKDF derived key and storing this encrypted key in the backup metadata. This assumes that an attacker would ever have the backup key ciphertext but not the backup metadata: given they are both stored on the homeserver and protected by the account credentials, this seems highly unlikely. + +### Option 2 + +The backup encryption private key, K-1 is generated by a secure random number generator. A private key, K-1p is generated by running PBKDF on the passphrase. K-1p' is generated by XORing K-1 with K-1p. K-1p' is stored on the along with the key backup in the `private_key` object above. The recovery key is generated by encoding K-1 as above. + +To change the passphrase, the client generates the new K-1p from the new passphrase then computes a new K-1p'. It then updates the backup information with this new K-1p'. + +This would require the API to support updating the metadata stored with a backup (or the key parameters to be stored elsewhere, eg. in account data). + +This option, however, allows the server to obtain K-1 by obtaining any one of the users previous passphrases, assuming it keeps copies of the previous versions of the key parameters. This option is therefore not viable, but included for completeness. + +### Option 3 + +The backup encryption private key and a private key, K-1p and K-1p' are generated as above. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. -We deliberately encrypt the Curve25519 private key rather than deriving it from the passphrase so that if the user chooses not to store their encrypted recovery key on the server, they can benefit from a Curve25519 key generated from a high entropy source of random rather than being needlessly derived from a passphrase of some kind. (TODO: Is this accurate)? +To change the passphrase, the client starts a new backup version as in option 1, but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. ## Security considerations @@ -43,4 +66,4 @@ In future, we could consider supporting authenticating users for login based on ## See also: -Notes from discussing this IRL are at https://docs.google.com/document/d/11fF1rbX5eTkrfxXRS8UhpW5sBENOCydYlLWzB8X1IuU/edit \ No newline at end of file +Notes from discussing this IRL are at https://docs.google.com/document/d/11fF1rbX5eTkrfxXRS8UhpW5sBENOCydYlLWzB8X1IuU/edit From 8ab9ecef28622526fedda16ac0231de2a2fe08de Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 10:49:09 +0000 Subject: [PATCH 04/10] missed a -1 --- proposals/1687-encrypted-recovery-keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index 4fa4f5ff71f..cdf50d1ceaa 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -52,7 +52,7 @@ This option, however, allows the server to obtain K-1 by obtaining an ### Option 3 -The backup encryption private key and a private key, K-1p and K-1p' are generated as above. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. +The backup encryption private key and a private key, K-1p and K-1p' are generated as above. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K-1. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. To change the passphrase, the client starts a new backup version as in option 1, but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. From f66f0f507b0b853fc8f47f3491bdc95188e1a0af Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 10:50:20 +0000 Subject: [PATCH 05/10] Clarify that a new K is generated --- proposals/1687-encrypted-recovery-keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index cdf50d1ceaa..d072766684c 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -54,7 +54,7 @@ This option, however, allows the server to obtain K-1 by obtaining an The backup encryption private key and a private key, K-1p and K-1p' are generated as above. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K-1. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. -To change the passphrase, the client starts a new backup version as in option 1, but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. +To change the passphrase, the client starts a new backup version as in option 1 (generating a new K-1), but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. ## Security considerations From 3f282affe307cb1c9e65b660b59e22ff5803235f Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 10:55:04 +0000 Subject: [PATCH 06/10] Add definitions of the various K dirctly in option 3 rather than referring to option 2 --- proposals/1687-encrypted-recovery-keys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index d072766684c..019203bff05 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -52,7 +52,7 @@ This option, however, allows the server to obtain K-1 by obtaining an ### Option 3 -The backup encryption private key and a private key, K-1p and K-1p' are generated as above. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K-1. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. +The backup encryption private key, K-1, and a private, passphrase-derived key, K-1p are generated as above.The passphrase key counterpart, K-1p', is also generated as above from the K-1 XOR K-1p. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K-1. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. To change the passphrase, the client starts a new backup version as in option 1 (generating a new K-1), but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. From 80abfe202d8e466b8efc1420b186147544713e3d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 10:56:53 +0000 Subject: [PATCH 07/10] word wrap --- proposals/1687-encrypted-recovery-keys.md | 126 +++++++++++++++++----- 1 file changed, 102 insertions(+), 24 deletions(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index 019203bff05..169a26b2ce5 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -2,28 +2,49 @@ ## Problem -[MSC1219](https://github.com/matrix-org/matrix-doc/issues/1219) proposes an API for optionally storing encrypted megolm keys on your homeserver, so if a user loses all their devices, they can still recover their history. The megolm keys are public-key encrypted using a private Curve25519 key that only the end-user has. - -However, there are usability concerns about users having to store their Curve25519 recovery private key in a secure manner. Casual users are likely to be scared away by having to file away a relatively long (e.g. 10 word) generated recovery key. - -We would like to give the user the option to access their key backup using a passphrase in addition to their recovery key. We can take inspiration from Apple’s [FileVault 2](https://hal.inria.fr/hal-01460615/document) where Apple store encrypted copies of your FileVault AES key on your hard disk, encrypted by your UNIX account password, or a passphrased SSH private key on a server for convenience. +[MSC1219](https://github.com/matrix-org/matrix-doc/issues/1219) proposes an API +for optionally storing encrypted megolm keys on your homeserver, so if a user +loses all their devices, they can still recover their history. The megolm keys +are public-key encrypted using a private Curve25519 key that only the end-user +has. + +However, there are usability concerns about users having to store their +Curve25519 recovery private key in a secure manner. Casual users are likely to +be scared away by having to file away a relatively long (e.g. 10 word) +generated recovery key. + +We would like to give the user the option to access their key backup using a +passphrase in addition to their recovery key. We can take inspiration from +Apple’s [FileVault 2](https://hal.inria.fr/hal-01460615/document) where Apple +store encrypted copies of your FileVault AES key on your hard disk, encrypted +by your UNIX account password, or a passphrased SSH private key on a server for +convenience. ## Proposed solution -Three solutions are given here (two of which are viable, one included for completeness), varying in the implications of the user changing their passphrase. +Three solutions are given here (two of which are viable, one included for +completeness), varying in the implications of the user changing their +passphrase. ### Recovery Key -In all options below, the process for generating a recovery key from a byte string, b is as follows: +In all options below, the process for generating a recovery key from a byte +string, b is as follows: * Prepend the two bytes 0x8B, 0x01 to the byte string b - * Compute a parity bit by XORing all bytes of the resulting string (ie. prefix + `byte string`) + * Compute a parity bit by XORing all bytes of the resulting string (ie. prefix + + `byte string`) * Append the parity byte to the prefix + b - * base58 encode the resulting byte string with alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'. - * Format the resulting ASCII string into groups of 4 characters separated by spaces. + * base58 encode the resulting byte string with alphabet + '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'. + * Format the resulting ASCII string into groups of 4 characters separated by + spaces. ### Option 1 -The user provides a passphrase, P. The client generates the backup encryption private key, K-1 by running PBKDF on this passphrase. The PBKDF parameters are stored in an object in the auth_data of the key backup under the 'private_key' key: +The user provides a passphrase, P. The client generates the backup encryption +private key, K-1 by running PBKDF on this passphrase. The PBKDF +parameters are stored in an object in the auth_data of the key backup under the +'private_key' key: ```json { @@ -34,36 +55,93 @@ The user provides a passphrase, P. The client generates the backup encryption pr } ``` -The backup public encryption key, K, is determined by running the curve25519 function on K-1 with basepoint {9}. The recovery key is then generated by encoding K-1 as above. +The backup public encryption key, K, is determined by running the curve25519 +function on K-1 with basepoint {9}. The recovery key is then +generated by encoding K-1 as above. -To change the passphrase, a client creates a completely new backup version, performing the steps above with the new passphrase. The client then re-encrypts all sessions keys and uploads them to the new backup. The user will always get a new recovery key whenever they change their passphrase. +To change the passphrase, a client creates a completely new backup version, +performing the steps above with the new passphrase. The client then re-encrypts +all sessions keys and uploads them to the new backup. The user will always get +a new recovery key whenever they change their passphrase. -In this option, the recovery key is generated directly from the passphrase using PBKDF. This means the ciphertext of the backed up keys is more vulnerable to dictionary attacks. We could mitigate this by randomly generating the backup recovery key, encrypting it somehow with the PBKDF derived key and storing this encrypted key in the backup metadata. This assumes that an attacker would ever have the backup key ciphertext but not the backup metadata: given they are both stored on the homeserver and protected by the account credentials, this seems highly unlikely. +In this option, the recovery key is generated directly from the passphrase +using PBKDF. This means the ciphertext of the backed up keys is more vulnerable +to dictionary attacks. We could mitigate this by randomly generating the backup +recovery key, encrypting it somehow with the PBKDF derived key and storing this +encrypted key in the backup metadata. This assumes that an attacker would ever +have the backup key ciphertext but not the backup metadata: given they are both +stored on the homeserver and protected by the account credentials, this seems +highly unlikely. ### Option 2 -The backup encryption private key, K-1 is generated by a secure random number generator. A private key, K-1p is generated by running PBKDF on the passphrase. K-1p' is generated by XORing K-1 with K-1p. K-1p' is stored on the along with the key backup in the `private_key` object above. The recovery key is generated by encoding K-1 as above. +The backup encryption private key, K-1 is generated by a secure +random number generator. A private key, K-1p is generated +by running PBKDF on the passphrase. K-1p' is generated by +XORing K-1 with K-1p. +K-1p' is stored on the along with the key backup in the +`private_key` object above. The recovery key is generated by encoding +K-1 as above. -To change the passphrase, the client generates the new K-1p from the new passphrase then computes a new K-1p'. It then updates the backup information with this new K-1p'. +To change the passphrase, the client generates the new +K-1p from the new passphrase then computes a new +K-1p'. It then updates the backup information with this +new K-1p'. -This would require the API to support updating the metadata stored with a backup (or the key parameters to be stored elsewhere, eg. in account data). +This would require the API to support updating the metadata stored with a +backup (or the key parameters to be stored elsewhere, eg. in account data). -This option, however, allows the server to obtain K-1 by obtaining any one of the users previous passphrases, assuming it keeps copies of the previous versions of the key parameters. This option is therefore not viable, but included for completeness. +This option, however, allows the server to obtain K-1 by obtaining +any one of the users previous passphrases, assuming it keeps copies of the +previous versions of the key parameters. This option is therefore not viable, +but included for completeness. ### Option 3 -The backup encryption private key, K-1, and a private, passphrase-derived key, K-1p are generated as above.The passphrase key counterpart, K-1p', is also generated as above from the K-1 XOR K-1p. Another private key, K-1r is generated also by a secure random number generator and encoded to give the recovery key as above. K-1r' is generated by XORing K-1r with K-1. Both K-1p' and K-1r' are stored in the `private_key` in the backup under keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. - -To change the passphrase, the client starts a new backup version as in option 1 (generating a new K-1), but additionally computes a new K-1r' by XORing K-1r with the new K-1. This refreshes all keys, but allows the user to keep the same recovery key for their backup, on the assumption that the recovery key itself has not been compromised. If it has, the client generates a new backup with a completely fresh recovery key instead. +The backup encryption private key, K-1, and a private, +passphrase-derived key, K-1p are generated as above.The +passphrase key counterpart, K-1p', is also generated as +above from the K-1 XOR K-1p. Another private +key, K-1r is generated also by a secure random number +generator and encoded to give the recovery key as above. +K-1r' is generated by XORing K-1r +with K-1. Both K-1p' and +K-1r' are stored in the `private_key` in the backup under +keys `passphrase_counterpart` and `recovery_key_counterpart` respectively. + +To change the passphrase, the client starts a new backup version as in option 1 +(generating a new K-1), but additionally computes a new +K-1r' by XORing K-1r with the new +K-1. This refreshes all keys, but allows the user to keep the same +recovery key for their backup, on the assumption that the recovery key itself +has not been compromised. If it has, the client generates a new backup with a +completely fresh recovery key instead. ## Security considerations -The proposal above is vulnerable to a malicious server admin performing a dictionary attack against the encrypted passphrases stored on their server to access history. (It's worth bearing in mind that the server admin can also always hijack its user's accounts; the thing that stopping them from impersonating their users is E2E device verification.) +The proposal above is vulnerable to a malicious server admin performing a +dictionary attack against the encrypted passphrases stored on their server to +access history. (It's worth bearing in mind that the server admin can also +always hijack its user's accounts; the thing that stopping them from +impersonating their users is E2E device verification.) ## Possible extensions -In future, we could consider supporting authenticating users for login based on their encrypted passphrase, meaning that users only have to remember one password for their Matrix account rather than a login password and a history-access passphrase. However, this of course exposes the user's whole E2E history to the risk of dictionary attacks by public attackers (i.e. not just server admins), keysniffer-at-login attacks or clients which are lazy about storing account passwords securely. There's also a risk that because login passwords are much more commonly entered than history passwords, they might encourage users to force a weaker password. It's unclear whether this reduction in security-in-depth is worth the UX benefits of a single master password, so we suggest checking how this proposal goes first (given in general we expect key recovery to happen by cross-verifying devices at login rather than by entering a recovery key or passphrase). +In future, we could consider supporting authenticating users for login based on +their encrypted passphrase, meaning that users only have to remember one +password for their Matrix account rather than a login password and a +history-access passphrase. However, this of course exposes the user's whole +E2E history to the risk of dictionary attacks by public attackers (i.e. not +just server admins), keysniffer-at-login attacks or clients which are lazy +about storing account passwords securely. There's also a risk that because +login passwords are much more commonly entered than history passwords, they +might encourage users to force a weaker password. It's unclear whether this +reduction in security-in-depth is worth the UX benefits of a single master +password, so we suggest checking how this proposal goes first (given in general +we expect key recovery to happen by cross-verifying devices at login rather +than by entering a recovery key or passphrase). ## See also: -Notes from discussing this IRL are at https://docs.google.com/document/d/11fF1rbX5eTkrfxXRS8UhpW5sBENOCydYlLWzB8X1IuU/edit +Notes from discussing this IRL are at +https://docs.google.com/document/d/11fF1rbX5eTkrfxXRS8UhpW5sBENOCydYlLWzB8X1IuU/edit From 89e25566fbc36b52619da9a4fda5b74c7b7d3b3e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 31 Oct 2018 17:15:51 +0000 Subject: [PATCH 08/10] Try & clarify mitigation against dict attacks splits option 2 up into two sub-options --- proposals/1687-encrypted-recovery-keys.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index 169a26b2ce5..c800e84d842 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -66,14 +66,9 @@ a new recovery key whenever they change their passphrase. In this option, the recovery key is generated directly from the passphrase using PBKDF. This means the ciphertext of the backed up keys is more vulnerable -to dictionary attacks. We could mitigate this by randomly generating the backup -recovery key, encrypting it somehow with the PBKDF derived key and storing this -encrypted key in the backup metadata. This assumes that an attacker would ever -have the backup key ciphertext but not the backup metadata: given they are both -stored on the homeserver and protected by the account credentials, this seems -highly unlikely. +to dictionary attacks. Option 2b attempts to offer a mitigation against this. -### Option 2 +### Option 2a The backup encryption private key, K-1 is generated by a secure random number generator. A private key, K-1p is generated @@ -96,6 +91,18 @@ any one of the users previous passphrases, assuming it keeps copies of the previous versions of the key parameters. This option is therefore not viable, but included for completeness. +### Option 2b + +A variant on option 2a is to regenerate K-1 when the passphrase is +changed, meaning the recovery does change when the passphrase is changed, +making it identical feature-wise to option 1 and without the problem of any +previous passphrase being sufficient to obtain K-1. It differs, +however, in that K-1 is generated randomly and therefore not +vulnerable to dictionary attacks. However, K-1p is still +vulnerable to dictionary attacks and is stored in the same place with the same +protection, and, if compromised, gives access to K-1. This option +therefore offers no significant security benefit over option 1. + ### Option 3 The backup encryption private key, K-1, and a private, From c26bd4fb0d6ae77e4503e6bf6826e61a94e81ffa Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 21 Jan 2019 13:39:44 +0000 Subject: [PATCH 09/10] Add note saying which we picked. --- proposals/1687-encrypted-recovery-keys.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index c800e84d842..ffcf07623fb 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -26,6 +26,9 @@ Three solutions are given here (two of which are viable, one included for completeness), varying in the implications of the user changing their passphrase. +Option 1 has been chosen, on the basis that we do not require the user to +be able to change their passphrase without also changing their recovery key. + ### Recovery Key In all options below, the process for generating a recovery key from a byte From 2ffb58cdc87452905c60580386b750347166959c Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 21 Jan 2019 14:14:02 +0000 Subject: [PATCH 10/10] Fix spec to match what got implemented --- proposals/1687-encrypted-recovery-keys.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/proposals/1687-encrypted-recovery-keys.md b/proposals/1687-encrypted-recovery-keys.md index ffcf07623fb..e8e8df4e3d3 100644 --- a/proposals/1687-encrypted-recovery-keys.md +++ b/proposals/1687-encrypted-recovery-keys.md @@ -46,15 +46,14 @@ string, b is as follows: The user provides a passphrase, P. The client generates the backup encryption private key, K-1 by running PBKDF on this passphrase. The PBKDF -parameters are stored in an object in the auth_data of the key backup under the -'private_key' key: +parameters are stored in the auth_data of the key backup under +'private_key_salt' and 'private_key_iterations' keys, respectively: ```json { - "private_key": { - salt: "MmMsAlty", - rounds: 100000 - } + [...] + "private_key_salt": "MmMsAlty", + "private_key_iterations": 100000 } ```