Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a recommended way to store derived keys in the browser? #45

Open
Rorymercer opened this issue Jan 12, 2021 · 5 comments
Open

Comments

@Rorymercer
Copy link

I am successfully deriving a key with crypto_pwhash() that I then split into a public and private x25519 key in the browser environment. This is derived from an end user's passphrase.

Is there a recommended way to store the Private Key safely in the browser environment? I know that for libraries based on WebCryptography API, Keys can be marked as non extractable and then the object saved to IndexedDB - but I don't think keys generated through Sodium-plus have similar extractability property. Is there a recommended way to store private keys in the browser for a session?

@pfiadDi
Copy link

pfiadDi commented Feb 9, 2021

Hi can I ask how you did that? I am able to use crypto_pwhash() to derive a key from the password but how you get the key pair - I would like to use crypto_box_seed_keypair() but this function isn't available in this library...

@Rorymercer
Copy link
Author

@pfiadDi this is the code I was using (have now moved on as couldn't find a satisfactory way round this issue)

let sodium = await SodiumPlus.SodiumPlus.auto();

let salt = await sodium.randombytes_buf(16);

let keyPair = await sodium.crypto_pwhash(64,password,salt,sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);
            
let publicKey = await sodium.crypto_box_publickey(keyPair);
let secretKey = await sodium.crypto_box_secretkey(keyPair);

@pfiadDi
Copy link

pfiadDi commented Feb 11, 2021

@Rorymercer - that is interesting thank you!
I did it now this way (also starting from the crypto_pwhash but resulting in a 32 bit key):

    let privateKey = new X25519SecretKey(pwhash.getBuffer())
    let publicKey = await sodium.crypto_box_publickey_from_secretkey(privateKey)

I am not sure if there are differences in security - do you?

EDIT: btw, about storing - what I like about the deterministic approach ist that you don#t have to store any keys if you know the salt and the password. I want to send the stored salt to the client, user enters password and keys are generated on the fly and discarded after the operation.

@akhan619
Copy link

@pfiadDi - There might be a problem with your approach. I had a similar requirement which I will explain in the second half of this post, something the OP @Rorymercer might be interested in as well.

Section 1:

The crypto_pwhash() function as per the documentation can be used for password hashing and key derivation. The key derived from this can then be used to encrypt among other things the private key in an asymmetric key pair. The problem with @pfiadDi code is that the there is no new key that is actually generated. See the code below

let salt = await sodium.randombytes_buf(16);

let keyPair = await sodium.crypto_pwhash(32,'Hello',salt,sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);

keyPair.getBuffer().toString('hex')
"aad71f3c0ee4ffda486920c2f649e8d3685cd9cbaa7b8f43861b2ab2c32364ba"

let privateKey = new X25519SecretKey(keyPair.getBuffer())

privateKey.getBuffer().toString('hex')
"aad71f3c0ee4ffda486920c2f649e8d3685cd9cbaa7b8f43861b2ab2c32364ba"

As one can see the key generated from the hashing is the same as the private key. Now this might be what you are trying to do but I am not sure about the crypto aspect of it, i.e. security. This might be ok but in essence we are mixing hashing algorithms with public-private key generation algorithms.

Section 2

So back to the original question: How to derive public-private keys (maybe for end-to-end encryption) on a client machine and then store it safely. We need 2 sets of keys for this - a KEK (Key-encryption-key) and the DEK (data-encryption-key). Both can be built using sodium-plus.

Step1: Generate a KEK using user supplied password and a randomly generated salt.

let alice_salt = await sodium.randombytes_buf(16); <- This must be saved. Can be plaintext. Store on client/server

let alice_key = await sodium.crypto_pwhash( 32, alice_password, alice_salt, sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE );

Step2: Generate a DEK

let aliceKeypair = await sodium.crypto_box_keypair();

let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);

let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);

One can use the above keys for exchanging data. Note I have only shown one set of key pairs. In a real system you would need every user to have such a set of keys.

Step3: Encrypt the private key for storage - Can be stored on client machine or on a server.

let alice_nonce = await sodium.randombytes_buf(24); <- This must be saved. Can be plaintext. Store on client/server

let alice_ciphertext = await sodium.crypto_secretbox( aliceSecret.getBuffer(), alice_nonce, alice_key ); <- This is the encrypted private key using the KEK generated earlier. Store on client/server

So as we can see the only thing that we need to reverse the above steps are the 2 salts and the user password. You can refer to the counter parts of the above functions in the documentation for that. Hope this helps.

@pfiadDi
Copy link

pfiadDi commented Jun 28, 2021

thx @akhan619 - I'll check it out but it look great. thank you for the extended explanation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants