2

I'm building a REST API that uses API keys for authentication. I want to ensure that these API keys are stored securely in my database. If I were storing user passwords, I would use Argon2id for hashing. For other types of sensitive user data that require decryption, I would consider using AES encryption.

However, I'm unsure about the best practices for storing API keys. Specifically, I want to know:

  1. Should I hash API keys before storing them in the database, similar to user passwords?
  2. If hashing is recommended, what algorithms are best suited for this purpose?
  3. Are there any specific considerations or best practices for securely storing and managing API keys that differ from handling passwords or other sensitive data?

I reviewed the OWASP guidelines but couldn't find a definitive recommendation on this topic.

1 Answer 1

4

An API key is a password. It has different characteristics than a typical human password, but it is a string of characters that will allow the bearer to authenticate.

So it should be:

  1. Totally random, not derived from the account ID or any other identifier
  2. Stored using a one-way function, Argon2id with the recommended parameters is fine, but bcrypt and salted SHA2-256 are popular choices.
  3. Automatic rotation of API key on first use
  4. Possible for an administrator to change the API key in case of compromise.
  5. Possible to change the one-way algorithm at deployment time. Storing the algorithm used is one way to do it, but you might get away with deducing the algorithm from lenght of the one-way image you stored.

Crypto agility

You must be able to change the algorithm at deploy time. I would use the lenght of the image to know what algorithm was used, leaving to my future self the trouble of removing an extra, unused random character added to the image in case you have to switch to an algorithm that has the same output lenght.

This table will give you a sense of the time it takes to brute force a bcrypt'd API key with high end hardware. If you are worried about nation state attack, add a few characters to your API key.

Automatic rotation

Whoever comes across the API key will know it. Your system should have an API endpoint that will exchange a temporary API key (known by administrators and developers) with the real API key.

The protocol is simple :

  1. When an API call is made with an API that needs to change, you return a 401 error code.
  2. The application has logic to handle the error by calling the "renew key" API.
  3. The application gets a new API key and stores it without human intervention
  4. The API call is retried.

Change the API key in case of compromise

You can use the mechanism above to implement rotation of the key by an administrator. They just go in, change the API key and are done. The key does not have to be shown to them.

If you believe the API key was compromised, then you are back to the initial onboarding process. An administrator will be shown the new API key, passed along to developpers and configured in the application. Using it will return a 401 that will trigger the process above.

+If you developers can easily read the key from the production configuration, that is a different problem.

Sign up to request clarification or add additional context in comments.

7 Comments

My issue comes from that exact reasoning: API keys are used as passwords and share some of their characteristics, but they aren't the same. I understand using Argon2id for password hashing due to the low quality and entropy of typical user-chosen passwords. However, API keys are often used differently. While passwords are typically used to establish a session for subsequent requests, API keys are validated with each request, meaning the authentication process is performed repeatedly.
I'm concerned that opting for Argon2id, given its nature and the recommended configuration (19 MiB of memory, an iteration count of 2, and 1 degree of parallelism), will significantly impact system resources. If I understand correctly, each hash operation would require 19 MB of RAM, meaning 100 concurrent requests would consume approximately 1.9 GB of RAM.
Moreover, the configuration with a parallelism degree of 1 and two iterations could place a considerable load on the CPU. My initial reaction is that this overhead seems excessive, and there might be a better compromise between security and performance. However, I also recognize that this might be wishful thinking, and the security benefits might justify the resource costs. As a professor of mine once said, "Those who choose performance over security deserve neither."
Sorry, I thought using Argon was a requirement. You have to think of your threat model (someone stealing your database and running hashcat on your hashes. Even broken unsalted MD5 it will last a little while if you have a 16 random alphanumerical, mixed case API key. Use a salted SHA2-256 or bcrypt and sleep tight. +That's why you must make it easy and part of the API contract to be able to change the API key at runtime. I'll edit my answer to include that.
Thank you for the clarification. I realize now that I wasn't entirely clear about my requirements. Interestingly, you've given me even more to consider. I see that this is quite a complex topic. Allowing users to programmatically change their own API keys is an intriguing idea that hadn't occurred to me, but it makes perfect sense. Typically, users generate and revoke API keys through an interface, but managing this at the API level would simplify integration and reduce the number of people who see the API key before it is committed to Vault, for example.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.