In general, solutions that do this work something like the following. You should find a trusted system that already does this and use it, rather than trying to build one yourself.
- First, generate a symmetric key. This is the Master Key and no user will ever see it directly. Do not store this key (in plain text) anywhere an attacker could potentially reach it.
- Use the Master Key to encrypt the data, and ideally also HMAC it for integrity (or use some other way of ensuring integrity, such as using AES in GCM mode).
- Run the user's password through a Key Derivation Function (like PBKDF2 or scrypt) using different parameters (such as a different salt) than is used for the password verifier. Do not store these Password-Derived Keys anywhere at all.
- Encrypt the Master Key once with each user's Password-Derived Key. Store these encrypted copies of the Master Key with each relevant user's account data.
To access the data:
- Authenticate the user. (This typically means retrieving the password verifier parameters for their account, running their password through the key derivation function that uses those parameters to produce a password verifier, and then checking whether that password verifier matches the one stored for that user.)
- Get the Password-Derived Key for the user. (Retrieve the second set of KDF parameters, and pass the password into your KDF using those parameters; we already know the password is correct so you can expect that the re-derived key is correct without checking it against anything.)
- Using the Password-Derived Key, decrypt the Master Key. Purge the Password-Derived Key from memory as soon as possible after doing this.
- Using the decrypted Master Key, verify the data's HMAC (or otherwise ensure its integrity) and then decrypt the data.
- Either purge the decrypted Master Key from memory and then access the data (in which case you'll need to repeat steps 2-4 if you want to modify the data and save the changes), or hold onto the decrypted Master Key in as secure of memory as you can arrange.
- Purge the decrypted Master Key, and the decrypted data, from memory as soon as possible.
To change a user's password:
- If the user has their old password (password rotation), verify the old password and then use it to generate the user's current Password-Derived Key and with that, decrypt the Master Key. Using the new password, generate the user's new Password-Derived Key and re-encrypt the Master Key with it. Replace the old encrypted Master Key with the new one (in the user's account info).
- If the user has forgotten their old password, have an administrator verify the user's identity through some other means, and have the user select a new password. Then have the administrator get the decrypted Master Key by entering their own password, deriving their own Password-Derived Key, and using it on their own copy of the encrypted Master Key. Using the user's new password, generate the user's new Password-Derived Key and use it to encrypt the Master Key. Place the encrypted-with-the-new-key Master Key in the user's account information, replacing the one that was encrypted using the key derived from the old (forgotten) password.
To manage users:
- For new users, basically just follow the "forgotten password" flow above.
- To delete a user, or even just revoke their access to the data, delete the copy of the Master Key encrypted using that user's Password-Derived Key. This makes it impossible for the user to use their own password to get the Master Key by any means.
Despite the detail (which is only present to help you understand the system), PLEASE do not attempt to implement this yourself. There are numerous additional critical details that are either missing or mentioned only in passing. For example, it is vital that the user never directly see the Master Key (otherwise they can store a copy of it, removing your ability to effectively revoke their access without regenerating the entire key structure and re-encrypting the data). Many of these details (such as secure key storage in memory) will be platform-dependent.
Additionally, there are many ways to screw up the crypto itself, even if using library implementations of the cryptographic primitives; if you generate your keys or key derivation parameters incorrectly, and choose the wrong primitives, or implement any of the steps in a way vulnerable to side-channel attacks, your whole scheme could be compromised. Don't try to figure this stuff out on your own; go with something that somebody knowledgeable about crypto implementation has already put together, and that somebody else knowledgeable has reviewed.