The admin of the website needs to read user data. So, if the key is derived from the user pass, then the admin has to know the user pass, not exactly a good idea.
The following encryption scheme was very kindly suggested by Polynomial.
Essentially what you're attempting to do is backdoor each user's account in a way that allows only the user and a single administrative user to access their data. This can be achieved as follows:
us = User salt = Random unique value (not the same salt as used for authentication)
as = Admin salt = Random unique value (not the same salt as used for authentication)
uk = User key = pbkdf2(user_pass, us, 256, rounds)
dk = Data key = random 256-bit key
ak = Admin key = pbkdf2(admin_pass, as, 256, rounds)
P = RSA private key
p = RSA public key
uk' = Encrypted user key = dk ^ uk
dk' = Encrypted data key = RSA_Encrypt(dk, p)
P' = Encrypted private key = AES_Encrypt(P, ak)
m = message
iv = initialisation vector (random unique value)
c = ciphertext = AES_Encrypt(m, iv, dk)
We then store uk', dk' and iv with the data record we're encrypting. We store us with the user account record. We also store P' and as with our admin account record. The public key P can be stored in the code. The keypair is common between records.
The user decryption works as follows:
Compute uk from the user password and us.
Xor uk' with uk to retrieve dk.
Decrypt c using iv and dk, giving us m.
The admin decryption works as follows (the private key is stored offline):
Compute ak from the admin password and as.
Decrypt P using AES_Decrypt(P', ak)
Decrypt dk using RSA_Decrypt(dk', P)
Decrypt c using iv and dk, giving us m.
I've tried to implement Polynomial's scheme in PHP: this is the result. Now I'd like to know if my implementation is correct and if it is secure.
Thanks a lot for your very useful help!