The operation isn't trivial; you not only need to encrypt data, you need to make it safe against theft or break-in on the server; you also need to consider what happens if you need to change the key.
To encrypt the images, your best choice is probably mcrypt with symmetric algorithm. There are several tutorials on how to do that. See What is the best way to encrypt files in AES-256 with PHP? .
What you need to do is serve an image and decrypt it on the fly, without ever storing it in the clear.
Now the problem is where to keep the password. The best overall solution is to have a secret encryption key for every user, generated at random on user enlistment; and the key is itself encrypted asymmetrically with the user's password. You can use phpseclib
to do that in RSA. The same encryption key is also stored asymmetrically with an administrative password.
So the workflow is:
- user logs in (and supplies his password)
- the system compares the password hash with a stored hash and grants access (you will use something such as bcrypt: see Is there a bruteforce-proof hashing algorithm? )
- the system also decodes the encryption string and stores it in temporary session (persistence of session might be a problem to address!)
- all that user's images are now accessible to him
To encrypt the image:
- if the user uploads the image, then at upload time the encryption key is available in the session. Use it :-)
- if the images are uploaded some other way, then an "already encrypted/still to be encrypted" flag is set in the database, and as soon as possible (administrator logs in / user logs in) all pending encryption operations are performed.
The image is then stored as an encrypted file ("001823040.bin"). There is no reference, except in the database, on which user does it belong to; and not knowing the user (hence the encryption key), the image is unrecoverable.
To serve the image you just set the headers to the image type, then start decoding the file and output it in the clear to the user's browser.
From an attacker's point of view, having the image alone is no use, for they are encrypted. The user database is useless because the encryption keys themselves are encrypted. Stealing or bruteforcing one user's password only gains access to that user's encryption key, which is different from all the others, and then there's still the task of finding which images belong to that user.
If you need to change the user's password, you can still recover the encryption key using the administrative password and reencrypt it asymmetrically with the user's new password. This cannot be done automatically because it would mean storing the now all-important admin password in the clear on the system; so all users that forget their passwords get lined up in a batch, and in the morning the administrator receives a notification "There are 75 people waiting for password restoration", supplies the password, and unlocks them all.
It's awkward, but all other solutions unfortunately rely on the password being available locally in plaintext, where a security compromise would leave the system wide open.
(You can sometimes work around this limitation by setting up a second, small, very limited - and therefore not so vulnerable - server to act as an encryption escrow service; but it's much more complicated to maintain).
Multiple users/passwords scenario
You have resource A in one table, and several users U1, U2, ... Un with access to that resource. That means that user U1 must be able to access the decryption key for resource A.
You can do this by storing a UserAccessToResource table:
user_id -- the user ID
resource_id -- the resource ID
encryption -- resource decryption key, encrypted with the user's encryption **key**
To grant access to user U1 and resource A, you have to have access to resource A yourself, and to U1's encryption key. That is:
- the administrator accesses the Users table and recovers UserKeyEncryptedWithAdminKey.
- being Administrator, he can decrypt UserKey.
- in the same way he accesses ResourceTable[A].ResourceKeyEncryptedWithAdminKey
- he encrypts ResourceKey using UserKey and stores into UserAccessToResource
The user U5 comes by and can quickly verify that (5, 42, ??) is present in UserAccessToResource table, so he knows he can access the resource. Retrieves the row, and decrypts the ResourceKey. He can now access Resource[42] and decrypt it with the ResourceKey (but no other resources, since they have different and random ResourceKeys).
In all this, the front-end has never access (barring programming errors) to the actual value of ResourceKey (or UserKey). The API is something like DecryptResource(UserId, UserPass, ResourceId)
and returns a decrypted resource.
Of course, the UserAccessToResource may contain any number of users or resources - it's many to many. For each one, an encrypted key has to be stored (say 32 hex bytes for a AES entry).
Lost password scenario
Unfortunately, this operation cannot be done automatically. The user sends a request, but his keys are not accessible until and unless an Administrator logs in. So he'll have to wait.
When the admin does log in, the system is able to access the PasswordRecovery table and find the user's incomplete record. It accesses the user table and retrieves UserKeyEncryptedWithAdminKey.
It now generates a random key and completes the record in the PasswordRecovery table with the UserKey encrypted using that one random key. Another column holds the words "Squeamish Ossifrage" encrypted with the same random key. He sends a secure email to User 123, supplying the random key.
User 123 supplies the random key to the system. The admin is no longer logged in, but the system can check that once decrypted, the second table field is indeed 'Squeamish Ossifrage'. It then assumes that the first field is the decrypted UserKey. The system requests a new password from User123, and stores the new UserKeyEncryptedWithUserPass into the Users table, clearing the entry from PasswordRecovery. Only one record has been updated. All resource keys owned by User 123 remain encrypted with User 123's key, that has not changed and is still the one generated at random upon account creation by the Admin.
The next login, the user supplies the password and unlocks the system.