User higher level encryption libraries. Use safe defaults. Do not invent new cryptography schemes.
Isolate encryption logic and keep it modular.
Assume client side security model is flawed and we can depend only on the server being safe.
Assume server is unsafe and we can rely only on client side security model.
Secret Key Cryptography - Symmetric encryption. A shared key decrypts, anything with that one key can decrypt
Public Key Cryptography - Asymmetric encryption. A public and private key pair exists. Public allow encryption. Private allows decryption.
We formerly used AES and RSA encryption, we prefer to use generic terms now that do not imply any one implementation. We defer to libsodium recommendations on algorithms
High level review
We have Users, Groups, and Secrets. Users and Groups have their own RSA keys. A Secret is any arbitrary data encrypted symmetrically. The Secret key cryptography is used to encrypt secrets is stored encrypted for a user or group's public key. Thus the user or group is able to decrypt the key and decrypt the secret.
Combined Secret Key + Public Key Pair encryption is done with simple-asymmetric-js which is just a wrapper around libsodium). Sometimes we do crypto in Python, but this is only for rapid prototyping and testing of the back end.
User to Group to Secret process
A user has a public and private key. The private key is stored encrypted on the server and can be decrypted using a user's master password (what they log in with).
A group has it's own public and private key. The private key is stored encrypted with an secret key which itself is encrypted with a user's public key. A copy of these keys must be stored for each user in the group. While we know the group has only one private key, there is no database record for this. Instead each Group-User (A many to many "through" table) has it's own encrypted copy. If decrypted we would find these keys are all the same private key for the group.
Once we know the group private key - we can use this to decrypt a Secret. Again we'll use a combined asymmetric + symmetric approach - The Group's private key will decrypt a secret key which can then decrypt our secret. Secret's hidden data must be copied for each User or Group with access to the Secret. Hidden data is a PostgreSQL HStore field which allows it to store any key value. The key is not encrypted. For example a key may be "password" while the hidden data is "hunter2" though in the database we would find only cyphertext.
Note a User can have direct access to a Secret and not use a Group. In this case the user's private key would decrypt the Secret's secret key.
What if a libsodium algorithm we use turns out to be flawed.
If libsodium's default algorithms turn out flawed that's a big problem for lots of people. Our strategy would be to upgrade data over time to the newer algorithm. Django's password hashing is a good example of this. When a user logs in Django will automatically rehash their password and save it in the newer format, as well as some meta data about which algorithm was used.