1. Clarifying the attacker model
You need to decide, what sort of attacks you want to protect from. The general eavesdropping adversary model suggest that the attacker can access your database and your entire system any time.
Another, more specific assumption can be mobile eavesdropping adversary. The "mobile" attributive relates to the fact that the adversary does not have access to your system any time, only for a limited time - for instance, somebody breaks in to your server and you detect the attack, patching the system, and with that move you "remove" the attacker. The attacker might break in again, but until that point the new information is considered to be secure. Any data accessible on your server during the time window of compromise considered to be leaked in full.
You may also consider, that some special hardware (e.g. HSM) stored private keys cannot be compromised - whether this assumption is correct or not depends on the further capabilities of the attacker
You also need to consider outsider or insider attack - outsider can be a hacker from the internet, insider can be a rogue employee.
It is important to note, what axapaxa pointed out clearly: an active attacker might be able to change the behavior of your site, and steal the passwords or do other malicious changes to gain access to the information. Protection against an active attacker is hard, would require digital signing of your webserver resources on a way, that the user can check it before connecting to your service - for instance, checking the code signature on your javascript, signed with a key not available on your webserver (so, TLS does not count...). Unfortunately, there is no such a standard - yet.
So, I will stick with the passive/eavesdropping adversary assumption. Still a nice challenge to solve :).
2. Encryption architectures
On the market, you can categorize the existing solutions for this matter based on two standpoints:
- where is the key managed and persistently stored
- where is the encryption process takes place
Key management categories:
- Managed by the site provider (this is you) with the site (e.g. simple database encryption)
- Managed by the site provider in a different system (e.g. a HSM or other special hardware)
- Managed by the end-user, in its own datacenter/on-premise server (e.g. HSM is provided by the end-user, your system get temporal keys from such hardware)
- Managed by the end-user, on its end-device (e.g. private keys in the browser; or the password of the user if you don't store it on your webserver)
Encryption process categories:
- Encryption takes place at the site provider's server (e.g. using simply a software encryption lib, with a key anywhere above)
- Encryption takes place at the site provider's, separate subsystem (e.g. using a special hardware, HSM, for the encryption process as well, not just for key management)
- Encryption takes place at the end-user on-premise server (e.g. encryption gateway like CipherCloud)
- Encryption takes place at the end-user device (e.g. using JavaScript to encrypt).
I will not go through all of the combinations here (16 cases) in details, only the end-device key management and encryption.
Note, that your suggested version falls into the category of End-device managed key with site provider managed encryption process. This can limit the damage of a mobile eavesdropping adversary, if you regularly patch and check the system. If you want to go down that road, that is fine, even not the most secure setup, I would suggest the following:
- Use PBKDF2, or preferably bcrypt, or scrypt (the last is the best) to derive a good secret from the password. Use random salt + at least 1000, but preferably 10,000 iterations, with scrypt sufficient size of memory map (larger the better, but webserver is a tricky question...). Let's say that the secret derived from the password is X.
- DO NOT store X for password validation.
- Use HKDF-Extract with random salt to derive from X (let's say, V), or derive from the password using PBKF2/bcrypt/scrypt using the same parameters as for X, but with a different IV. Store V for password validation.
- Use HSM or other special hardware to do the server side encryption of the data.
- DO NOT encrypt data with HSM+X, rather encrypt a key-encryption-key K. Encryption of actual data with K can be done outside of a HSM, simply using a crypto lib on your webserver.
- I suggest using AES-GCM with random IV for data encryption, AES-KW for key wrapping. You may also consider format-preserving encryption modes IF, and ONLY IF there is an unavoidable size constraint for the data, because format-preserving encryption modes are less secure than AES-GCM with random IV.
3. End-device managed key with end-device managed encryption (aka. end-to-end encryption)
You asked about a website, so I will focus only on WebCrypto API capabilities - an encryption library for JavaScript, provided by modern browsers. For applications, you may use other cryptolibs.
With WebCrypto, you can generate a secure random key, to use it for encryption. Encryption can take place at the frontend of the website, for instance before submiting a form, a JavaScript encrypts its content with AES-GCM. The keys can be stored in the local storage of the browser, which is never shared with your website, unlike cookies - it is kept local all the time.
However, if you want that a user access the information from another computer, I suggest that you use PBKDF2 to derive a secret from user password (using proper salt and proper number of iterations for stretching) on the browser side. Then, using this derived key, you can wrap the encryption keys with AES-KW - all supported by WebCrypto API.
This way, your server will not get access to the user password any-time, unless you do the password authentication properly. If you want to authenticate the user, do not send the password to your server, because that is an actual disclosure and the whole point of keywrapping, encryption will slip on this banana... instead of that, use PBKDF2 derived secret for authentication, with a completely random salt, different from what is used for encryption. You may also consider using SRP, SPAKE, AugPAKE, etc. for authentication - however, those are not supported by WebCrypto.
If you do not want to implement those, you may also consider using 0_kit, a project I am involved in, which provides all those features integrated, on top of WebCrypto and other JavaScript implementation of crypto algorithms.