I want to store files (images) on a public webserver and let users see them if they know a password. The server shouldn't have the unecrypted files and the server can only serve files, not perform any server-size computation.
One thing that I know about crypto is that I know very little so I'd like a review of the following plan:
Description of the idea:
I plan to encrypt the images with AES-256, random IV, and save them as a concatenation of IV and encryption output. Some images will have the same passwords, some won't.
To view these files, the user will type his password into an input box on a webpage and javascript will download the encrypted file and use the password that he typed in to decrypt the image and display it.
The download and decryption of images is slow and I expect it to fail if the password is wrong so I'd like to store a "password check" that will execute locally, in javascript. My idea is to store, in a plain-text "index file", some sort of hash of the correct password. (The index file is far faster to download than the image.) The javascript will locally check if the hash of the password matches what's listed in the index file before downloading and displaying images.
I plan to create the index file and encrypted images in .Net and do all the javascript with crypto-js, so any algorithm that I use should be available with those. I want to minimize download times and javascript run time if it doesn't compromise security.
Questions:
How do I expand the user's password into a valid AES key? So far, I plan to use PBKDF2 because it's available in .Net and crypto-js.
- Should I have a separate salt for each image?
- If so, should I store that salt in the encrypted image with the IV/encrypted-file or in the index file? What advantages/disadvantages are there to the options? For instance, I could do PBKDF2 while waiting for the image to download.
- Should I have a separate salt for each image?
How do I store the password check? So far, I plan to use PBKDF2 for this, too, and put a random salt and PBKDF2 output in the index file. The per-password salts would be different from the per-image salts, of course, because otherwise the AES key for each image would be in plain text in the index file!
- Is there any reason that PBKDF2 would be a bad choice for both password-check and image-decrypt?
Any security mistakes that I didn't think of? I know that downloaded javascript can't be trusted but I'll let my users decide if they want to take that risk or use https to get the javascript.
Edit, resolution: My resolution is Ilmari Karonen excellent answer. Per valid password, I generate 128-bits with PBKDF2, each time with a new, random salt. I store the salt per password in the index file. Per photo, I generate a random IV and random key and encrypt. In the photo index file, per photo, I store the photo's key, encrypted by the per-password PBKDF2. I also store a second encryption of the photo's key, this time with the first byte of the photo's key incremented by 1 modulo 256. That is used to check that the password, already verified as valid for some photos, is valid per-photo. Doubling the PBKDF2 bits for authentication purposes is far slower than just attempting all the image-key decryptions and testing the results.
This is crypto, not stackoverflow, so I won't include the code here but all the encrypting is done with .Net and all the decrypting in javascript and it works. I can provide it freely if someone wants it.