5 ms·
My adventure in designing API keys
- vjay15 6mo agoHello everyone this is my third blog, I am still a junior learning stuff ^_^
- notpushkin 6mo agoHey, welcome to HN! Reading “hex” pointing to a clearly base62-ish string was a bit interesting :-) Also, could we shard based on a short hash of account_id, and store the same hash in the token? This way we can lose the whole api_key → account_id lookup table in the metashard altogether.
- vjay15 6mo agoHello thanks for reading through my blog :D Coming to your question, yes! that is possible I mentioned it in my second approach! But when I mentioned it to my senior he wanted me to default with the random string approach :)
- vjay15 6mo agoI NEVER THOUGHT I WOULD BE IN THE MAIN PAGE OF HACKERNEWS THANK YOU SO MUCH GUYS (╥﹏╥)
- calrain 6mo agoI don't like giving away any information what-so-ever in an API key, and would lean towards a UUIDv7 string, just trying to avoid collisions. Even the random hex with checksum component seems overkill to me, either the API key is correct or it isn't.
- andrus 6mo agoGitHub introduced checksums to their tokens to aid offline secret scanning. AFAIK it’s mostly an optimization for that use case. But the checksums also mean you can reveal a token’s prefix and suffix to show a partially redacted token, which has its benefits.
- sneak 6mo agoIdentifying an opaque value is useful for security analysis. You can use regex to see when they are committed to repos accidentally, for example.
- dhruv3006 6mo agoHey - this was a great blog ! I liked how you used the birthday paradox here. PS : I too am working on a APIs.Take a look here : https://voiden.md/ https://voiden.md/
- MORPHOICES 6mo ago[dead]
- deleted 6mo ago[deleted]
- ramchip 6mo agoThe purpose of the checksum is to help secret scanners avoid false positives, not to optimize the (extremely rare) case where an API key has a typo
- vjay15 6mo agothank you so much ram chip :) I didnt know that!
- matja 6mo agoI suppose there could be two checksums, or two hashes: the public spec that can be used by API key scanners on the client side to detect leaks, and an internal hash with a secret nonce that is used to validate that the API key is potentially valid before needing to look it up in the database. That lets clients detect leaks, but malicious clients cant generate lots of valid-looking keys to spam your API endpoint and generate database load for just looking up API keys.
- ramchip 6mo agoThat second hash is called a Message Authentication Code (MAC), it's what the JWT HS256 algorithm does
- usernametaken29 6mo agoI know sometimes people just like to try things out, but for the love of god do not implement encryption related functionality yourself. Use JWT tokens and OpenSSL or another established library to sign them. This problem is solved. Not essentially solved, solved. Creating your own API key system has a high likelihood of fucking things up for good!
- fabian2k 6mo agoYou don't need any encryption or signing for API keys. Using JWTs is probably more dangerous here, and more annoying for people using the API since you now have to handle refreshing tokens. Plain old API keys are straightforward to implement. Create a long random string and save it in the DB. When someone connects to the API, check if the API key is in your DB and use that to authenticate them. That's it.
- iamflimflam1 6mo agoI would add the capability to be able to seamlessly rotate keys. But otherwise, yes, for love of everything holy - keep it simple.
- swiftcoder 6mo ago> Plain old API keys are straightforward to implement This is pretty much just plain-old-api-keys, at least as far as the auth mechanism is concerned. The prefix slug and the checksum are just there so your vulnerability scanner can find and revoke all the keys folks accidentally commit to github.
- vjay15 6mo agoyes this is the approach!
- sabageti 6mo agoWe don't store it, in plain text right, store them hashed as always.
- 6mo ago
- bob1029 6mo agoI don't understand the need for this level of engineering. It appears we are going for an opaque bearer token here. The checksum is pointless because an entire 512 bit token still fits in an x86 cache line. Comparing the whole sequence won't show up in any profiler session you will ever care about. If you want aspects of the token to be inspectable by intermediaries, then you want json web tokens or a similar technology. You do not want to conflate these ideas. JWTs would solve the stated database concern. All you need to store in a JWT scheme are the private/public keys. Explicit tracking of the session is not required.
- notpushkin 6mo ago> The checksum is pointless because an entire 512 bit token still fits in an x86 cache line I suppose it’s there to avoid round-trip to the DB. Most of us just need to host the DB on the same machine instead, but given sharding is involved, I assume the product is big enough this is undesirable.
- phire 6mo agoYou need to support revocation, so I'm not sure it's ever possible to avoid the need for a round trip to verify the token.
- kukkamario 6mo agoThe point of the checksum is to just drop obviously wrong keys. No need to handle revocation or do any DB access if checksum is incorrect, the key can just be rejected.
- ben-schaaf 6mo agoThat sounds like it's only helpful for ddos mitigation, in which case the attacker could trivially synthesize a correct checksum.
- phire 6mo ago
- weitendorf 6mo agoHey OP, sorry for the negativity, I think most of these commenters right now are pretty off-base. My company is building a lot of API infrastructure and I thought this was a great write up!
- vjay15 6mo agoIt is alright, I am learning a lot from them as well, healthy criticism is always useful :) I am very glad that you found this a great write up ^_^
- Savageman 6mo agoSide note: the slug prefix is not primarily intended for the end-user / developer to figure out which kind of key it is, but for security scanners to detect when they are committed to code / leaked and invalidate them.
- vjay15 6mo agoAhhhh I see, I didn't think about it that way too, this could help us a lot yea!!!
- randomint64 6mo agoWhile it's true that API keys are basically prefix + base32Encode(ID + secret), you will want a few more things to make secure API keys: at least versioning and hashing metadata to avoid confused deputy attacks. Here is a detailed write-up on how to implement production API keys: https://kerkour.com/api-keys https://kerkour.com/api-keys
- vjay15 6mo agoThank you! I will definitely look into it!
- jeremyloy_wt 6mo agoI don’t understand your explanation on mitigating the confused deputy. If the attacker has access to the database, can’t they just read the IDs for the target row they are overriding first so they can generate the correct hash?
- randomint64 6mo agoThe attack would be like: attacker has read/write access to the database but not to the code of the backend service. Attacker swaps the hash of a targeted API key with the hash of their own API key. Attacker has now access to the resources of the targeted organization when using their own API key.
- 9214063141 6mo agoInteresting read, I do have some questions though and hope you could answer them: 1. Why do you use the API key ID AND the organization ID, and not just one of them, to prevent the confused deputy problem? 2. Why is not necessary to use something like Argon2id for hashing? You say "our secret is already cryptograhically-secure", but what does this mean exactly? Is it due to the fact that the secret is already very high entropy and cracking it, even if we use much faster hash functions like the ones mentioned in your article, it would practically not be possible even PQ with highly parallelized hardware? Anyways, very interesting read, thank you!
- tlonny 6mo agoPresumably because API keys are n bytes of random data vs. a shitty user-generated password we don’t have to bother using a salt + can use something cheap to compute like SHA256 vs. a multi-round bcrypt-like?
- vjay15 6mo agoI can't understand what you are trying to say :o
- numbsafari 6mo agoHow are you storing the API key in your database?
- vjay15 6mo agohash of the API key just like passwords
- stanac 6mo agoI think they are saying passwords are salted and we use multiple rounds of hashing to prevent rainbow tables and slow down brute-forcing the password (in case of db leak). We don't need to do that for randomized long strings (like api keys), no one is guessing 32 character random string, so no salt is needed and we don't need multiple rounds of hashing.
- vjay15 6mo agoOHHH that makes sense!
- agwa 6mo agoCorrect. Even a million rounds of hashing only adds 20 bits of security. No need if your secret is already 128 bits.
- pdhborges 6mo agoI don't even understand what approach 3 is doing. They ended up hashing the random part of the API key with an hash function that produces a small hash and stored that in the metashard server is that it?
- vjay15 6mo agoyea... sorry I still am not the best explainer but that is the approach, I just wanted to have a shorter hash in the meta shard that is it. The approach 3 is an attempt by me to generate my own base62/base70 encoder ;-;
- adaptit 6mo ago[dead]
- grugdev42 6mo agoEverything about this is over engineered. Just KISS.
- petterroea 6mo agoA bit over-engineered, but it was fun to read about observations on industry standard API keys. I agree it would be nice with more discussion around API keys and qualities one would want from them.
- amelius 6mo agoIt's a bit confusing that the "Random hex" example contains characters such as "q" and "p".
- sneak 6mo agoThis is a very good example of premature optimization.
- matja 6mo agoWhat if the "slug" was a prefix for the API key revocation URL, so the API key was actually a valid URL that revoked itself if fetched/clicked? :)
- vjay15 6mo agobut api keys arent meant to be revoked once used right?
- out_of_protocol 6mo agoi suspect a lot of tools will try to fetch the url without explicit user action (e.g. messengers do that kind of crap). Gotta be hard to keep keys non-revoked, which is a nice side-effect
- hk__2 6mo ago> I didn't proceed with this approach since I don't want the API keys to have any info regarding the account, but hey it is all just a matter of preference and opinion. Well I would have done that and saved half the blog post.
- codingjoe 6mo agoIs this running in a production environment yet? If so, do you have an email address to disclose a vulnerability?
- vjay15 6mo agono this is just a POC, I haven't implemented any of it
- codingjoe 6mo agoOk, then for everyone. Don't save tokens in a database. Selects are vulnerable to timing attacks. You want a token to include a id and a signature. The ID is used to look up the scope or user attached to the token, while the signature is recreated from the ID, the server secret and some salt. The resulting signature is double checked with the provided signature with a time constant comparison. An attacker will be able to identify valid keys, but won't be able to sign them. You can either split the values like aws or join them with a separator. Good idea with the slug though, makes it easier to report leaked tokens to the issuer.
- tjarjoura 6mo agoI've always been interested in the technical distinction between an API "key" and an API "token". And the terminology of "key" used to confuse me, because I associated that with cryptography, and I thought an API key would be used to sign or encrypt something. But it seems that in many cases it's basically just a long, random password.
- vjay15 6mo agoYes, it's just a random long password used to access public APIs
- Serhii-Set 6mo ago[dead]
- SyndicateLinks 6mo ago[dead]