2
$\begingroup$

I am designing an web api which needs to grant access to various client apps via an api key sent as http header. I know, not really how it should be done but I have no control over this part.

My current design for api key: have 16 bytes for the app id (a guid) in the database + 16 bytes randomly generated (keybytes). Due to company policy I was asked not to store api keys in the db so I store a sha256 hash of (clientid + keybytes + salt) and the salt in the database.

Whenever a request comes in, I split the api key into (id, keybytes), lookup the client in the database by id, then calculate sha256 (id + keybytes + salt-from-db) and check if it matches the database stored hash. The requirements from the corporate security team was to not store api key in the db but store a hash of it (just like an user password). I also considered PBKDF2 (I am using .net 5) but it might be too slow for doing it every incoming reuest (it has to run for each request because I cannot change the client apps to login via a slow call then send a token, they are supposed to just send the key with every request).

Basically, this is just like storing sha256(password+salt) in the db for an user, so I am really not sure if it's "good enough" (given that password is random 16 bytes and user id is another random 16 bytes - the app guid and the db is theoretically not accessible).

An alternative I have for the api keys is to use AES-GCM: generate random nonce (iv) and encrypt the app id (guid) with a secret key (stored only in the webapi settings, not db) and use the bytes (nonce, encryptedid, tag) as api key. This would mean no database lookup in case of bad api keys. I am, however, not sure about how secure this solution is (afterall, I am encrypting only one 128 bit block of data - the appid guid - with a key, among all apiclients).

Which one is "acceptable" (if it's both, I'll take the first one since it's already implemented) in case of a "security breach (database gets stolen for example) ?

I know, if they get to the db, the api key is the least of my concerns, but I would still like to hear an opinion....

$\endgroup$

1 Answer 1

2
$\begingroup$

Whenever a request comes in, I split the api key into (id, keybytes), lookup the client in the database by id, then calculate sha256 (id + keybytes + salt-from-db) and check if it matches the database stored hash. The requirements from the corporate security team was to not store api key in the db but store a hash of it (just like an user password). I also considered PBKDF2 (I am using .net 5) but it might be too slow for doing it every incoming reuest (it has to run for each request because I cannot change the client apps to login via a slow call then send a token, they are supposed to just send the key with every request).

If the key remains secret and consists of 16 fully randomized bytes then there is no need for PBKDF2; a secure hash is already one way, and it's not possible to brute force a 128-bit key. The other random 16 bytes in the UID add icing on the cake, as it avoids the birthday problem.

Basically, this is just like storing sha256(password+salt) in the db for an user, so I am really not sure if it's "good enough" (given that password is random 16 bytes and user id is another random 16 bytes - the app guid and the db is theoretically not accessible).

With a 128 bit key it doesn't really matter how much other random information is in there. It may be better to use a true KBKDF (key based key derivation function) or HMAC though, there the key is used as input keying material. You should also make sure that the inputs are in a canonical encoding - but if the fields all have predefined sizes then you can just concatenate them.

An alternative I have for the api keys is to use AES-GCM: generate random nonce (iv) and encrypt the app id (guid) with a secret key (stored only in the webapi settings, not db) and use the bytes (nonce, encryptedid, tag) as api key. This would mean no database lookup in case of bad api keys. I am, however, not sure about how secure this solution is (afterall, I am encrypting only one 128 bit block of data - the appid guid - with a key, among all apiclients).

You don't need confidentiality here, just verification of the input. So encryption seems to be an unnecessary complication. However, I do have a more pressing question: how do you protect the key, and how do you against replay attacks?

Quite often you can just store a random UID as token and let the rest be handled by TLS. That seems to be what most people do, but I'm not familiar with your use case. That also means that you should take everything of this advice with a grain of salt. I'd recommend getting a security professional to look at it and start off by defining what you need and who and what you need to protect against.

$\endgroup$
1
  • $\begingroup$ My use case is an desktop app (installed on premise) calling via https the api. How the app is secured is not my problem. :) I would have just used a random byte sequence as token but I received the extra requirement to not store it in the db as it is, hence the idea to treat the random byte sequence like a password and hash it. I do like that the aes-gcm solution doesn't require me storing anything, however I don't like the extra complexity (plus, if the master key is compromised, all api keys are). $\endgroup$
    – cobru
    Aug 30, 2021 at 15:59

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.