From cb9b64a184470c7f332eb2c20bf64d53e84406f1 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 23 May 2022 17:31:53 +0200 Subject: Add user secret in mix to encrypt keys with password --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 8880183..48d1088 100644 --- a/README.md +++ b/README.md @@ -61,19 +61,25 @@ Keys that are stored in K2V under PK `keys`: - if a password is used, `password:`: - a 32-byte salt `Skey` - followed a secret box - - that is encrypted with a strong argon2 digest of the password (using the salt `Skey`) + - that is encrypted with a strong argon2 digest of the password (using the salt `Skey`) and a user secret (see below) - that contains the master secret key and the curve25519 private key +User secret: an additionnal secret that is added to the password when deriving the encryption key for the secret box. +This additionnal secret should not be stored in K2V/S3, so that just knowing a user's password isn't enough to be able +to decrypt their mailbox (supposing the attacker has a dump of their K2V/S3 bucket). +This user secret should typically be stored in the LDAP database or just in the configuration file when using +the static login provider. + Operations: -- **Initialize**(`password`): +- **Initialize**(`user_secret`, `password`): - if `"salt"` or `"public"` already exist, BAIL - generate salt `S` (32 random bytes) - generate `public`, `private` (curve25519 keypair) - generate `master` (secretbox secret key) - calculate `digest = argon2_S(password)` - generate salt `Skey` (32 random bytes) - - calculate `key = argon2_Skey(password)` + - calculate `key = argon2_Skey(user_secret + password)` - serialize `box_contents = (private, master)` - seal box `blob = seal_key(box_contents)` - write `S` at `"salt"` @@ -87,12 +93,12 @@ Operations: - calculate `public` the public key associated with `private` - write `public` at `"public"` -- **Open**(`password`): +- **Open**(`user_secret`, `password`): - load `S = read("salt")` - calculate `digest = argon2_S(password)` - load `blob = read("password:{hex(digest[..16])}") - set `Skey = blob[..32]` - - calculate `key = argon2_Skey(password)` + - calculate `key = argon2_Skey(user_secret + password)` - open secret box `box_contents = open_key(blob[32..])` - retrieve `master` and `private` from `box_contents` - retrieve `public = read("public")` @@ -101,18 +107,18 @@ Operations: - load `public = read("public")` - check that `public` is the correct public key associated with `private` -- **AddPassword**(`existing_password`, `new_password`): +- **AddPassword**(`user_secret`, `existing_password`, `new_password`): - load `S = read("salt")` - calculate `digest = argon2_S(existing_password)` - load `blob = read("existing_password:{hex(digest[..16])}") - set `Skey = blob[..32]` - - calculate `key = argon2_Skey(existing_password)` + - calculate `key = argon2_Skey(user_secret + existing_password)` - open secret box `box_contents = open_key(blob[32..])` - retrieve `master` and `private` from `box_contents` - calculate `digest_new = argon2_S(new_password)` - generate salt `Skeynew` (32 random bytes) - - calculate `key_new = argon2_Skeynew(new_password)` + - calculate `key_new = argon2_Skeynew(user_secret + new_password)` - serialize `box_contents_new = (private, master)` - seal box `blob_new = seal_key_new(box_contents_new)` - write `concat(Skeynew, blob_new)` at `"new_password:{hex(digest_new[..16])}"` -- cgit v1.2.3