Why does your API still use HTTP Basic Auth?
swaggadocio.com
swaggadocio.com
A more sensible resolution to this issue, as a hypotherical security professional at an API company, would be moving more of the burden for security onto the company rather than the API consumer, by e.g. limiting the downside risk of a credentials compromise. (Similar to how banks don't say "The bad guys got your password? Sucks to be you, your balance is now $0", one would be well-advised to eventually have something on the roadmap to e.g. disallow unauthorized or poorly considered code from causing business catastrophe prior to getting a human in the loop.). You'd also want to e.g. make sure your sample code is secure of of the box, and write your first-party libraries to "just work" to the maximum extent possible, such that greenfield development would tend to be secured by default.
That's being pretty generic, but it's my understanding that many API companies actually do put quite of bit of work into internal anti-abuse tooling, for this and related reasons.
So just use HTTPS and leave it at that.
- Dont listen on HTTP, force HTTPS (Tune your ciphers! see https://www.ssllabs.com/ssltest/index.html)
- Ensure that any client libraries you supply for API interaction must verify the server certificate
- Only use the users password to obtain a long random key from the server either through an admin web interface where possible or by an API request that supplies the username and password and receives the key.
- Make authenticated requests of the API with the key
- Store any passwords as bcrypt hash in the DB for security
- Only store the key as SHA2-256 hash in your DB for performance
- Actually yes do use HTTP Basic as it's a convenient way to transmit the key (see how Stripe does it) making things easy for your users and perfectly secure as long as you prohibit plain HTTP.
- Stay the heck away from HMAC's and signing requests, you have SSL, properly tuned it's secure. HMAC is pointless here and really confuses the users.
But you should store "the key as SHA2-256 hash in your DB"
why hash it?
While you're likely in serious trouble if someone gets your DB this helps minimize damage. Besides to authenticate a request you dont need the plaintext of the key, a hash is fine.
You cannot brute force search against a large random number that is hashed with unsalted SHA or the like. This would require you to guess the number, hash it and check to see if the hashes match. A large random number encoded as a 64 character string is simply too big to guess, even at millions of guesses per second.
Thus using bcrypt to protect your api keys does nothing but impose a serious bottleneck in how many api requests per second you may authenticate.
It doesn't matter how many machines you have or how fast you can compute the SHA. An attacker will not find the API key (work out the math).
The reason you need bcrypt is that user supplied passwords are crap, and can be cracked very quickly.
EDIT: To make it clearer: "Long" is not explicit. 256 bits is. The difference between real randomness and for example output of RND in your favourite language is also nowhere to be seen. Moreover the use of the "key" is dubious as it's not an encryption key at all.
"Only use the users password to obtain a long random key" (emphasis mine).
So it was part of the initial recipe.
I guess the trade-off is here that the api key cannot then be retrieved from a GUI either (because the server doesn't actually know the original key!) .. from a UX perspective, most companies show your active keys in a GUI with ability to selectively remove or replace.
Though I can't think of a mainstream API that hasn't let me retrieve api key from GUI
Ideally you'd regenerate the key each time the user wants to show it or just use OAuth 2.0 (it's not as bad as everyone says it is).
How else can you guard against replay attacks, and authenticate the request? Only a key isn't enough, you need secrets as well. Right?
Use SSL only, tune ciphers and you're good. You're wasting time trying to overthink the problem by adding another layer on top.
Use a config line like the following in nginx:
ssl_ciphers ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH;
RC4 is a problem right now but unfortunately there's not a good solution until we get more TLS 1.2 support out there.
If you don't do this sort of tuning you're exposed to some weak ciphers that can result in attacks.
We've thought about closing HTTP access in the past. As with a lot of security, it's a usability trade-off.
We work hard to make sure that people only ever access Stripe over HTTPS. Most people use the Stripe API through an existing library. Since the API doesn't work over HTTP, any such library is guaranteed to use HTTPS. Stripe itself is on Chrome's built-in HSTS list, sets HSTS headers, etc.
The scenario Stevie describes is unlikely to be an issue unless the user is implementing his or her own library. In that case, though, they're almost certainly going to be using test API credentials. Stripe's API will never return a successful response when you access it over HTTP, so you'll figure out that you can't do this on the very first request.
More broadly, what exactly is being defended against isn't particularly clear. There are much easier ways for someone implementing their own library to screw up (skipping cert verification is extremely common and leaves you perpetually vulnerable to HTTPS MITM), and there are many other plausible vectors for an attacker.
Even if we did change this, we'd only have solved one small class of accidental key leaks. You could accidentally specify "stripe.com" instead of "api.stripe.com", for example, and stripe.com will of course always accept HTTP. Or you could typo the domain and send your key to someone else entirely.
If we ever did want to solve this, we'd probably do something like:
- Monitor HTTP Authorization headers sent to api.stripe.com.
- Notify the user if they ever accidentally send a secret key in the clear.
- In addition to notifying the user, we could potentially roll the key automatically (though this runs the risk of breaking production code).
But I don't think disabling HTTP helps much. (I do think it'd be good if we started monitoring the frequency with which this happens -- if it's common, we should certainly do something like the above; if it ~never happens, it presumably doesn't matter.)
Separately, I think that the HMAC-based signature scheme that Stevie proposes is something we might want to do at some stage. A very early version of Stripe actually had something very similar. The issue with all protocols in this vein is that they involve considerably more complexity on the user's end and are harder to debug. But they definitely have some nice security properties.
Maybe one recommendation should be supported vendor supplied SDKs which don't touch HTTP, only HTTPS. Another option is to detect credentials being sent over HTTP and notifying the clients, maybe deactivating their key if action isn't taken in a period of time.
The request won't go through and end up in a log somewhere if there's no MITM.
Summary of the article: http://tldr.io/tldrs/516fc7e340d4a84c4100020e/why-the-hell-d...
(see how I gave you a tl;dr of the link explaining how the tl;dr is generated?)
But this page is a bit outdated, this is what we use now: http://tldr.io/tldrs/514d1c2ec02bf6ba46001aba/more-on-the-tl...
Really, don't use HMAC-of-the password authentication tokens at all, though. They're not nearly as secure as TLS.
Better still, don't use passwords in your API at all. Passwords are for humans. APIs use keys.
You make great points, but just as a point of clarification (because I saw numerous people confused by this yesterday), this is merely nomenclature, no? What you are calling a key is really a long, random password (in Amazon's case the secret Access key).
A good password is a long string of random characters, and an API key is a long string of random characters, but they are not the same because the semantics are different. Passwords are typically chosen by customers, changed (let's be honest) very rarely and on a haphazard schedule, often reused across a bunch of different systems no matter how frantically we wave our hands, and (most important of all) have "user" semantics and scope: a password lets a user log in and do logged-in-user stuff.
Whereas an API key can be issued by the system, and is thereby guaranteed (assuming a clueful programmer) to be long and random and unique and not shared with the customer's Gmail account and bank account. You can expire it often. You can issue more than one per customer. (With passwords, this is theoretically possible but practically impossible; customers barely understand how to handle one password.) You can issue it to entities that aren't themselves customers, and that don't have "user" semantics. You can scope it to particular uses, issuing a key that is only good for checking balances but not for initiating transactions, for example.
---
[1] The other being cache coherency. And avoiding off-by-one errors when enumerating lists.
If someone gets a list of unencrypted (or poorly encrypted) passwords, they have the rights of the user. If someone gets a list of unencrypted (or poorly encrypted) keys, they have the rights of the API caller. The concerns seem identical.
The difference is that you can, and should, rotate your API keys automatically and fairly frequently (I will leave it to experts to tell us how frequently is prudent.)
Moreover, when you think your DB has been compromised you can flush all the API keys and reissue them. Depending on the application this may be more or less of a pain for the customers, but at least it is possible, because the keys are random and generated by you and not shared across services.
These facts mean that API keys, properly implemented, have a much smaller attack surface. You need not worry, for example, that a three-year-old backup file that someone harvested out of the trash at your ISP has valid API keys on it. The window for attack is, in fact, limited to "someone has stolen a very fresh copy of my live DB, but I have not yet realized it, and there is still something valuable left to compromise other than the data in my app's live DB." And this window often isn't very important. Often, closing it is akin to reinforcing the barn door after the horse has bolted and the barn has burned to the ground.
Passwords, on the other hand, get reused. You are using bcrypt to hash passwords not just to protect your app, but to protect your customer Gmail accounts that share the same password.
Since this API key is embedded within their app it'll require a programmer to change (and customers often may be non technical with sites built on contract). These are things you dont want to force customers to change with any frequency as it can cause pain and expense. You dont want to ever be forced to flush all your API keys, it could be a serious disaster.
Now there's protocols that rollover access keys hourly or the like such as OAuth a but that's a complex and often unnecessary use case that's beyond most API implementors. See how Google does OAuth 2.0 with their APIs for an example.
Since it's secure, simple and performant to hash API keys stored in your DB and just compared hashed values for authentication why would you ever not want to do it?
The correct next step after discovering that you may have lost custody of API key information is to ensure that the keys are worthless, not to scramble to try to protect the keys.
This is another reason you want to be clear about the difference between a "key" and a "password", because passwords are sticky. A benefit of working with keys is that they are not; you can zorch a key with minimal provocation.
I dont see any reason to continue the argument. It's clear to me that you should hash your API keys in your DB.
What am I missing?
Also, I'd say that a SHA256 hash of a 256-bit key is hardly a "half-measure". Nothing short of compromising your RNG or SHA2 will make those hashes useful to authenticate.
https://www.pcisecuritystandards.org/documents/pci_dss_v2.pd...
See section 8.5.9 for the password change policy. Section 3.6.4 for the key rollover policy (which references NIST 800-57 that basically says 1-3 years for most keys.
There's other standards (such as the 1-5 years for an SSL certificate from the issuer) and tons of best practices out there. In short you should be rolling over keys and expiring passwords on some sort of schedule. My point was that because humans use passwords they are conventionally seen as needing quicker expiration than keys.
There are lots of organizations with notions about passwords and keys and ciphers, etc. However in the context of this discussion I don't believe there is anything that could be considered a standard.
> this is merely nomenclature, no? What you are calling a key is really a long, random password
Both passwords and keys are shared secrets. A password is a secret a human remembers, and can prove he/she remembers. A key is something a machine stores and can prove it has.
(Actually, an API key can also be a token, eg: "{ hash: " + hash(key+salt+date(now))+"valid from: " + date(now) + "salt: " + salt + "}" -- that is -- if you know the key, you can verify that that the token is indeed valid from the date claimed (and you could add serial numbers and other stuff as well). Note that the user doesn't need to know the key in this case, the user only needs a copy of the token).
So yes, it is nomenclature, but it matters.
[edit: formatting, typo]
HTTP basic auth over SSL seems like the way to go - and you need to use SSL on your API anyway to prevent hijacking. The points about not allowing HTTP access to the API make sense, but avoiding basic auth entirely isn't a good security tradeoff.
or scrypt
The bit I disagree with is this:
As well as being tremendously simple, HTTP Basic by itself is also tremendously insecure, i.e. it is implemented by simply Base64 encoding the username and password concatenated with a colon “:” character. It then follows that HTTP Basic should only be used, if at all, over securely encrypted connections.
I agree - HTTP Basic is…well, basic. But you imply that even over SSL, HTTP Basic Auth is only marginally acceptable, and that's not the case. Everything you do securely online is transmitted in plaintext behind SSL. If a vanilla API key behind SSL is barely secure, you're pretty much hosed.
Once you use TLS is the "HTTP basic" often the optimal solution. After reading the whole article, I still don't understand why would anybody try to make HTTP "secure" instead of simply using TLS. So my question to the author, paraphrasing the article title:
Why the hell simply not using TLS and thereby avoiding the "basic auth is bad" misdirection?
And as we know, that needs to be done over TLS.
> HTTP provides a simple challenge-response authentication mechanism which may be used by a server to challenge a client request and by a client to provide authentication information.
The client should make two requests. The first with no credentials to see if the resource is protected (the challenge part). The server will then respond with a 401 Unauthorised status and a WWW-Authenticate header indicating what authentication to use. The client will then make another request with the given authentication.
The insecurity is caused by curl not doing this challenge response, it is just sending the credentials straight away.
My view is that cURL not a client. It's a lowish-level library and command line tool that can turn input into a format that an HTTP server can understand. Of course what puts a kink in this view is that it does things like follow redirects. A convenience for the user if anything. But in the end, you use cURL to build HTTP clients. It's not a client itself.
Regardless, you're right. It's a problem with the client not following the HTTP spec. However, I don't think many developers really follow the HTTP spec to begin with. For most, HTTP is URLS, headers, and a limited number of methods. I've certainly been guilty of viewing it as such in the past.
So in Platonic heaven, it's the client's fault and by extension the developer's fault. Does that means that API providers should let developers shoot themselves in the foot? Probably not.
A user agent that wishes to authenticate itself with an origin server--usually, but not necessarily, after receiving a 401 (Unauthorized) --
implying, imo, that no prior 401 response is needed. On the other hand, the spec also says
A client SHOULD assume that all paths at or deeper than the depth of the last symbolic element in the path field of the Request-URI also are within the protection space specified by the Basic realm value of the current challenge. A client MAY preemptively send the corresponding Authorization header with requests for resources in that space without receipt of another challenge from the server.
which would imply that at least one 401 response should be received before sending credentials.
Of course the most important part of the spec is this:
The Basic authentication scheme is not a secure method of user authentication, nor does it in any way protect the entity, which is transmitted in cleartext across the physical network used as the carrier.
See, for example, the Apache HttpClient documentation: http://hc.apache.org/httpclient-legacy/authentication.html#P...
If you don't provide credentials, curl will prompt you and do the authentication the server requests (if any). Using --anyauth will also let the server decide.
They're probably not going to change that default and break millions of scripts on the next apt-get upgrade.
HTTP Basic Auth works pretty well with SSL and should be enough for most cases. Also, it's dead simple for the API to return a token that can be used after authentication in non-SSL endpoints of the API.
While he does have a point, I do think part of the blame lies with the client side as well. If you're dumb enough to transmit user details over clear text protocols then you really shouldn't be allowed near any sub-systems which require authentication.
That just don't make any sense to me and implicitly suggests using other authentication methods.
As soon as you introduce e.g. MACs to the mix, you can forget about most unix-y commandline tools.
I think we can all agree that there are better options when it comes to authentication, but I'd rather have something I can work with easily than worry about that tiny amount of possible "plain http pre redirect" mistakes.
What about using http digest auth as a middle ground? It's still curl compatible and a tiny bit more secure.
It's also pretty simple to implement on client side and server side. I usually give our clients a little library to get started with requests.
I wouldn't call it simple. It's complex to get right, and outright damaging to non-web clients except in the case where xAuth or similar is available.
If you are implementing username/password, then Digest removes the ability to use a secure password system like Bcrypt or PBKDF2, which is a killer.
[1] You are able to store the digested tokens/passwords rather than plain-text, but if you need to protect the token/password in any way, the digest is an insecure way to do so.
I'm tempted to ask which command line tools you are missing in OSX, but I'm wondering if your capitalization was intentional and correct. :)
If the latter, I've found that 10-line ruby "clients" can go a long way toward simplifying API testing.
One of the main problems with Basic Auth that the author fails to mention is that if your passing passwords with every request, you're also hashing that password with EVERY request (assuming your doing REST, ie no client state on the server). And if you're properly hashing passwords in your DB, you should be using a slow hash like bcrypt. That means a single request that might take 30ms now takes perhaps 500ms. And many workloads are going to require multiple hits to the API to accomplish simple tasks, so your talking about both a strain on the system, and a horrible experience from the client's perspective.
If you're using basic auth and a long random server generated key as Stripe does instead of a user supplied pasword performance is a non issue as you'd SHA2 it in your DB not bcrypt.
(eg, as rb2k_ points out, it makes it easy to use curl to debug things).
Hope this can help raise awareness why it's bad.
This has the side effect of making sure that HTTP clients can never silently redirect, and it's impossible for a developer to ever get their code working without explicitly using SSL. If it never works in development, they'll never ship such code to production.
For Rails developers, Ryan Bates does a great job of explaining a few ways to secure your api.
However, I've recently been thinking about how to use a third-party API directly from client-side Javascript, or even from a resident application. If you're doing that, then you really need some way to use keys with validity that is limited by time or scope. In that case, HMAC offers some real advantages.
They are the same thing, both will not work.
(Unless you close the port 80, using HMAC-SHA can't solve the issue.)
In other words, use HTTP BASIC (or similar) if you want new users.
Until you can point me to an alternative authentication scheme that I can expect api consumers to generally have ready-made support for, it's pretty well the only good suggestion in this article.
Slinging OAuth shouldn't be necessary to hack your own fun scripts and get your own data.
foauth.org
Edit: File bug to browser makers. Personally use http proxy to remove auth and cookies from insecure traffic.
And I doubt you can convince any browser maker to drop basic auth support.
Also keep in mind that a machine storing plaintext passwords has the same security requirements as a Kerberos KDC, and those requirements are very, very high. Don't do this unless you know what you're doing, and even if you do, you should seriously consider a different scheme (or just using Kerberos itself: they've already gotten things right, so you don't have to risk getting things wrong).
Sounds insecure.
close port 80 on your API hostOnce the tcp connection is accepted on port 80, "dumb" clients (like curl) can just come barging through the door shouting plaintext auth credentials without knocking first, and no http server can stop them from doing that (because that is how the http protocol works).
The only way to stop them from doing that is rejecting connections on port 80. (Dropping packets looks even more like service outage, which was mentioned.)
This means that if you're going to use HTTP Basic authentication, you need to serve your API and your Website from different hosts.