You're opening an API for your users and need to give each of them a secret token. If this was just a password, you could just store a secure hash (scrypt, bcrypt) and call it a day. However, most services have an option to show your secret in the settings, which means that you need to be able to decrypt the secret token later.
I was surprised that there was little litterature on the subject, so I asked around and here's what people suggested:
1. Store in plaintext or encrypt with the key laying around
(encrypt with the key laying around means that the decryption key is on the internet-facing server, meaning that someone compromising your server can easily decrypt your tokens)
This is not as bad as it seems: the main reason why we hash passwords is because users might use the same password in multiple services. You can get compromised, but you don't want to leak thousands of Gmail accounts if it happens. You don't run this risk with secret tokens.
However, this is not entirely harmless:
If you get compromised, you'll have to revoke all your API secrets at once and effectively lock out all your users. Also, you might not realize that you've been compromised.
2. Don't store the secret token
If you open an API and are concerned about security, you could just display the token once and then only keep a hash. This is what AWS does.
Users would have the ability to generate a new secret if needed.
3. Encrypt with the admin password
You could encrypt the secret token with the admin password. Then, in the settings, you could have a "reveal" button that would users to confirm with the admin password.
There are few catches:
- You can have only 1 admin account.
- You still need to store a hash to be able to check the token (when there is a call on the API).
- You need to re-encrypt the secret whenever the admin password changes (you don't actually change the secret).
4. Have a separate infrastructure to manage secure data
Basically, the idea is that you store the token twice:
- As a secure hash, so you can easily verify it for each API call.
- Encrypted with RSA.
Then, you have 2 separate servers:
- An internet-facing front-end, server, that has the RSA public key: it can encrypt the token, but not decrypt it
- A separate server isolated from the internet, that has the RSA private key.
The isolated server offers an private API to front-ends: it will decrypt the token, but only after confirming the user's password. You assume that, being isolated from the internet and only having a tiny API, the isolated server is much harder to comprise.