19

I'm trying to design a small api however I'm a bit stuck on how to secure the api. I have read some articles about how to do this one of them is: Login and retrieving an apikey and then hash some values with this apikey and send the hashed string back along with the request, so it can be done again on server level.

Is this a good way or is this dangerous to do?

If not miss understood, to avoid man in the middle I can add the request url to the variables that will be hashed, or isn't that the appropriate way

Also my brain is stuck on how to use a time stamp to avoid making lots of request to the same url with same data.

I'm sorry if my question have been asked a 1000 times. However I have read some articles now and it's still not clear to me what way to go for my small api.

From what I have read and understand from it this should be the way.

  1. public key is stored in the application to let the user or application login.
  2. server creates private key for this particular user when it's accessed. Or should this be always the same or a static value that has been created by a person?
  3. user makes request sends along with the request a signature that's hash_hmac(some values+private key);
  4. server checks if these value's are correct and does by creating the same hash from the value's that are send.
  5. If server generates the same hash, the request is valid and can then be executed.
  6. Is this they way to go or am I missing some mayor things here.

For hasing the data is the underneath way a good way to create a hash?

$l_sPrivateKey = 'something returned by database when user loged in';
$l_aData = array();

foreach($_POST as $key => $value){
 if($key == 'signature') continue;
 $l_aData[$key] = $value;
}

//This should then be the same as $_POST['signature'];
hash_hmac('sha256',serialize($l_aData),$l_sPrivateKey, false); 

Your input would be appreciated. Kind regards and thanks in advance

Legionar
  • 7,472
  • 2
  • 41
  • 70
Iason
  • 372
  • 1
  • 5
  • 20
  • 7
    Don't reinvent the wheel, especially when security is a concern. There are existing libraries for this, use them. – Gerald Schneider Oct 26 '15 at 15:02
  • Ok apparently I haven't found them jet. Enlighten me please. – Iason Oct 26 '15 at 15:08
  • RSA is the way to go : [Encrypt and Decrypt text with RSA in PHP](http://stackoverflow.com/questions/4484246/encrypt-and-decrypt-text-with-rsa-in-php) – Tanuel Mategi Oct 26 '15 at 15:13
  • Have you stumbled upon client-side SSL certificates? Implementing authentication will require certain state-tracking so you can verify the responses. Using client-side SSL doesn't require that, people can use it in the browser and via CLI (curl). If you're interested, I could expand a bit on this. – Mjh Oct 26 '15 at 15:15
  • 2
    My recommendation is use JWT. It's quite easy to implement using [firebase/php-jwt](https://github.com/firebase/php-jwt). It's easy to setup works well in OAuth2-flow if needed, it's easier to create a valid token for development/testing, tokens are debuggable. – dbrumann Oct 26 '15 at 15:20
  • 1
    Thank you nice input @GeraldSchneider and Lason its actually a good thing to ask before implement or attempt something you don't fully understand the implementation you are trying to achieve has been implemented using Codeigniter 3 framework https://github.com/chriskacerguis/codeigniter-restserver – kay Oct 27 '15 at 07:20
  • @Mjh sounds interesting so yes maybe I should read a bit more about it! Can you help me a bit out ? – Iason Oct 27 '15 at 08:50
  • @dbrumann would have been a good option but we can't use composer. – Iason Oct 27 '15 at 08:50
  • @kay that's actually a good idea, we are now also using CI for our regular websites so I am familiar with how it works. However I didn't know that CI3 had these options. Worth trying some things out! – Iason Oct 27 '15 at 09:06
  • You can read about it [at this blog](https://rynop.wordpress.com/2012/11/26/howto-client-side-certificate-auth-with-nginx/). Idea is to provide a certificate to your client, which is signed by the certificate of the server. If the client doesn't provide the correct key while doing a request, then nginx denies it and request never comes to PHP. However, it's **much easier** to handle JWT as per @dbrumann's suggestion. – Mjh Oct 27 '15 at 09:43
  • I am always using oauth2 to protect api, client credentials part of oauth2. You have lot of examples on web. – pregmatch Oct 27 '15 at 21:44
  • The part of the question Abkht time stamp don't understand. Are you asking about bugs or about rate limiting the client so that they cannot use too much of the API? and what's the definition of too much do you mean high load or unfair or what as those may be subjective. That's a big question of itself please remove it from the question and ask it as its own question as it makes your main question less clear. – simbo1905 Oct 28 '15 at 11:59

1 Answers1

3

Secure Remote Password Protocol (SRP6a) With HMAC Fits Your Requirement

The following assumes that your API is browser-to-server so JavaScript-to-PHP not server-to-server using only PHP. SRP will work for both scenarios but the answer below discusses browser-to-server libraries.

Use the Secure Remote Password protocol to authenticate the user of the API which has the side effect of creating a strong session key. You can then use the shared strong session key to sign API requests and responses using HMAC.

RFC5054 uses SRP rather than public keys to create a shared session key to encrypt TLS traffic. There is an implementation in OpenSSL. This demonstrates that SRP authentication is a perfectly safe replacement to public keys to create a secure shared secret. IMHO using SRP is more convenient to solve your problem.

The Thinbus SRP library is a JavaScript SRP library which has a demo of authenticated to a PHP server. The PHP demo does not show using the shared session key but it is simply $srp->getSessionKey() on the server and client.getSessionKey() in the browser once the authentication protocol has finished. The default Thinbus configuration results in a 256bit shared key. You can use this with HMAC see the footnote 1 below about using signed JSON.

How It Works

The registration flow would be:

  1. Client API registration form generates a random API password using JavaScript at the client which is not transmitted to the server. This is saved into the browser local storage and shown to the user asking them to print it off and keep a backup.
  2. The password is given to the Thinbus SRP client JS library code which outputs a client salt and password verifier.
  3. The salt and verifier are posted to the server and saved in the database for that client. Normally Thinbus recommends you keep the verifier hidden by using HTTPS to send the verifier to the server to prevent brute force attacks to recover the password. If you are using a random generated password as long as a typical software license key then you can transmit the verifier over HTTP. See footnote 2 below.

The API usage flow would start with an SRP authentication of the client that has the side effect of generating a session key. Note all this is in the Thinbus demo code as "standard usage" but is explained here to give a flavour of how STP authentication works. This authentication protocol is shown in sequence diagram of the thinbus page and is running in the online demos:

  1. Client javascript loads the API password from browser local storage.
  2. Client AJAX fetches from the server the client salt and a server random one-time number B.
  3. Client javascript generates a one-time number A then uses the password, salt, and both one-time numbers to generate a session key K and hashes that with the both one-time numbers to create a password proof M that it posts to the server along with its random A.
  4. Server uses the password verifier saved to the database at registration, the client salt, and the two random numbers to compute the session key K then confirms the client sent password proof M is good. If that is all good it sends its own proof M2back to the client. At this point the client has been authenticated using STP as a zero-knowledge proof of password.
  5. Client checks M2 against its computation. If all is good both sides have a shared secret K which is a one time 256 bit session key derived from the random A and B that no man-in-the-middle can feasibly know.
  6. All API requests and responses can be HMAC signed with the shared secret and verified on the other side.

All of the above is covered in the PHP demo of Thinbus minus actually calling $srp->getSessionKey() at the end to have a key that can be used to sign things with using HMAC.

Give that SRP replaces password authentication with a cryptographic zero-knowledge proof of password it is surprising that not all developers use it by default. The fact that it also generates a shared session key for API signing is simply an added bonus.


Footnote 1: Most APIs would prefer to post one JSON value with all the data in it. This is because JSON is simple yet more powerful with built in API in both PHP and JavaScript to turn objects into strings and back again. As @dbrumann pointed in a comment there is a standard for signing JSON which is JWT. Google suggest that here are libraries for this in both PHP and JavaScript. So if you upgrade to passing one JSON input value and returning one JSON output for every command in your API Ayou can use a JWT library to sign and validate the JSON inputs and outputs of the API. One of the JWS algorithms is "JWSAlgorithm.HS256 - HMAC with SHA-256, 256+ bit secret". The libraries will sort out the mechanics of actually signing and verifying so you don't have to write that code and worry about possible security bugs.

Footnote 2: The recommendation with Thinbus is to transmit the password verifier to the server over HTTPS to keep the verifier secret. This is to prevent interception then an offline dictionary attack against the password verifier to recover the password (i.e. the password is salted into the verifier so you would need to run the 16G crackstation password dictionary through the verifier generation code with the user salt to find a match). With API usage the browser window.crypto API can generate a truly random "API key". Typically windows keys were 16 upper case letters shown to the user formatted as XXXX-XXXX-XXXX-XXXX. Checking the GRC password search space page it says that a random 16 letter upper case password that size would take a government 14 years to exhaustively search. Given that estimation you can safely transmit a password verifier generated for such a long random password over plain HTTP without encryption as no-one will feasibly dedicate many years of computing power to run so many password guesses through the verifier generation algorithm (which uses the random client salt so cannot be pre-computed) to find a match to recover the client API password.

Community
  • 1
  • 1
simbo1905
  • 6,321
  • 5
  • 58
  • 86
  • Some comments recommend oAuth. That will work yet but is a more complex multi-party authorisation protocol. SRP only requires one additions AJAX call to fetch the server challenge than regular password authentication. So SRP is a compact two party protocol. oAuth uses json signing so you can just use that with SRP for authentication and key exchange. – simbo1905 Oct 28 '15 at 09:01