# Implementation review - Independent key, admin side and user side

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
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!

• Written like a math major. I wish such things came with long-word variables. The age of transcription has passed us. </rant> Commented Jan 16, 2013 at 22:03
• @JeffFerland Why should we waste precious bits? ;) Commented Jan 16, 2013 at 22:12
• Space / sanity trade-off. Commented Jan 16, 2013 at 22:39
• I assume that salt1 and salt2 are us and as respectively? Commented Jan 17, 2013 at 14:21
• I didn't notice this when looking at the algorithm before, but what is the benefit of encrypting the private key with the admin key as opposed to the admin simply holding the private key offline. Assuming the admin key is common between records, is there any meaningful gain to use a different public/private key pair for each record? Or is that only done once and the same key pair is still reused, it just lets the admin avoid having to keep the private key local? Commented Jan 17, 2013 at 14:25

This is not unlike a scheme I posited in this question, which got some derision as overly complex. Yours is simpler (because user authentication is handled separately and only user data security/recovery is covered here), but there's still quite a bit going on here with encrypting encryption keys multiple ways. Necessary, probably, but still complex.

I would recommend two changes. First, the private RSA key, instead of being obfuscated behind a single admin account's credentials, could be stored offline. I'm assuming here that recovering user data is not an everyday occurrence, if an admin is required in order to do it (admins have better things to be doing all day than recovering user accounts). So, when it does need to happen, a user with admin privileges could retrieve an HSM (or a thumb drive with the keyfile if you're cheap) from physically-secured storage, and use it with an offline program (not part of the PHP app) to decrypt the user's data.

The advantage here is that it isn't dependent on one common admin account, and thus doesn't require one common admin account, avoiding the inevitability that the admin account credentials will be written down and subsequently fall into "enemy" hands. The key and/or recovery program would still be accessible only by someone with admin privileges, who would use their own personal access credentials to get the key from storage and to log into the program. This also gives you the possibility of an audit trail; you know exactly whose credentials were used to get the key out and run the program, so if there's ever a suspected compromise you can narrow the search.

You can implement whatever additional pomp and circumstance you deem necessary for retrieving and using the private key; just make sure the measures aren't so draconian that they make users want to circumvent them (or alternately, make sure the measures physically prevent circumvention; this usually requires some serious \$).

Second, I would also store p in the data layer, not in code. I realize that this scheme is in place in a PHP site, and so there's only one place to publish a code change that updates the public key. However, should the public key ever need to change, IMO it's still easier to go into the DB and update it than to publish the code change, and not that much more difficult to get the public key (which is, again, apparently not extremely common; it would only be needed when a user's dk changed, and even if that changes every session it's a trivial DB query to make).

Of course, changing p in the first place will make any dk' encrypted with a previous p obsolete. There are several ways to mitigate this:

• Decrypt and re-encrypt every user's data key whenever the RSA keypair changes.
• Store the "revision number" of the RSA key last used to encrypt a user's data key, with the current revision number stored alongside the current public key, and keep several past revisions of private keys in storage.
• Coupled with forcing users to change their password every 90 days (regenerating and re-encrypting their data keys) the number of old keys you'll have to keep should be pretty low.
• Force users to change their passwords when the RSA keypair changes, resetting and re-encrypting their data keys with the new public key.
• Thanks a lot! I've already considered that the adminside process will be offline... I've put together with the userside just to post only one link! In which cases should I change p? BTW as you can see, the key isn't hardcoded, but stored like a file. So it's easy to change it. What I'd like to know is if my implementation is coherent with Polynomial's scheme and if my impl. is safe. Thanks again Commented Jan 17, 2013 at 9:00
• Well, obviously the RSA keypair should be changed if it's ever believed to have been compromised. If you cannot account for all copies of the key data (and there should be only one) then generate a new keypair and get all the user records updated. Borrowing from X.509 certificates, it should probably also be regenerated at least once every three years, more like once a year, just on general principles. Lastly, it might be a good idea to generate a new keypair if there's ever any large change to the user data schema or the implementation. Commented Jan 17, 2013 at 15:43