I would like to know if deriving a key from a pseudo-random string with a single iteration is secure.
Concretely, I am designing a system where a secret key is derived in the client side, and then sent to the server for authentication. The steps are as follows:
- User enters email and password
- Derive
k0
<- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000) - Derive
k1
<- pbkdf2(password: k0, salt: password, alg: 'sha256', iteration: 1) - Send to server: (email,
k1
) - Server derives pbkdf2(password:
k1
, salt: somerandomstring, alg: 'sha256', iteration: 20000) and compares it with the hash in the database
k0
is the 256-bit secret key. The client uses it to encrypt things using a block cipher. The client needs to send something to the server for authentication purposes. But the server should not know the client's secret key. My idea is that step 3 prevents the server from knowing the client's secret key.
But I am wondering if 1 iteration of pbkdf2 is enough. I think it is sufficient because k0
is pseudo-random, and the attacker shouldn't be able to figure it out from k1
. (Exhaustive search takes 2^256 steps). Is this correct?
EDIT
For clarification, here is my design so far:
## Register
Client
* User enters email and password
* Derive k0 <- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000)
* Derive k1 <- pbkdf2(password: k0, salt: password, alg: 'sha256', iteration: 1)
* POST to '/register': (email, k1, kdf, kdf_iteration)
Server
* Generate salt <- 256bit random string
* Derive k2 <- pbkdf2(password: k1, salt: salt, alg: 'sha256', iteration: 20000)
* Create a user with (hashed_password, salt) = (k2, salt)
* Login the user and reply with a session key
## Login
Client
* User enters email and password
* GET '/prelogin': (kdf, kdf_iteration)
* Derive k0 <- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000)
* Derive k1 <- pbkdf2(password: k0, salt: password, alg: 'sha256', iteration: 1)
* POST to '/login': (email, k1)
Server
* Look up user (hashed_password, salt)
* Derive k2 <- pbkdf2(password: k1, salt: salt, alg: 'sha256', iteration: 20000)
* Check k2 == hashed_password, and reply with a session token
EDIT2
Clarification on what this is for:
This is to add end-to-end encryption for an open source note taking app (https://github.com/dnote/cli).
Basically, the client can sync the data with the server. Before leaving the client, all data is encrypted using k0
which server does not know.
The server should have no idea about how to decrypt the data, so I decided to derive k1
from k0
to send to the server rather than sending k0
directly to the server.
k0
is really acting as a proper secret key then, is it? Because in that case, the hash ofk0
is not really useful for any cryptographic purpose (e.g. you cannot execute any cryptographic primitive like a signature or encryption using the hash of the secret key instead of the secret key itself). Thus, what you probably mean is thatk0
is acting as the user password towards the server, essentially, and in that case you don't really need to use the double hashing; simply follow standard guidelines for password hashing (e.g. crackstation.net/hashing-security.htm). $\endgroup$