Wednesday, 8 April 2015

Token Based REST API Security

While writing REST APIs we sometimes wonder how to secure them. Hosting REST services over HTTPS will make the communication channel secured but not individual REST APIs. 

The possible options to secure REST API could be -



  • 1. HTTP Basic Authentication
  • 2. OAuth based Authentication
  • 3. Token Based Authentication



Lets see how we can use token based security for fully stateless REST APIs.  To understand toke based security we need to understand  -

ACCESS KEY: Access key is an unique key string passed in request header for every http request client sends to the server. We can consider access key as user identification for REST paradigm.

SECRET KEY: For every REST API user/client we generate a secret key. This secret key is used to encrypt some plain text to generate a cipher which will be used as request signature.

REQUEST SIGNATURE/MESSAGE HASH: Its the cipher text generated after encrypting a plain text using the secret key. This string is passed as the request signature.

HMAC(<some_plain_text> , <SECRET KEY>) = <SOME_REQUEST_SIGNATURE>

HMAC: A keyed-hash message authentication code (HMAC) is a specific construction for calculating a message authentication code (MAC) involving a cryptographic hash function in combination with a secret cryptographic key.

Using this concepts client will generate a request signature send it to server as request signature along with actual parameters and the access key. Lets move on to some code examples so that we can understand it better.

Code Example

Lets assume that we store user access and secret key in some database. So the database table will look like  - 
<rest_user_key>

id
app_id
access_key
hmac_key
aes_key
1
<some_app>
access_key1
sec_key1

2
<some_app>
access_key2
sec_key2

3
<some_app>
access_key3
sec_key3

4
<some_app>
access_key4
sec_key4



HMAC JAVA Code :

public class HMACKeyGeneratorServiceImpl {

private static final char[] symbols = new char[36];
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
private static final String SHA1PRNG_ALGORITHM="SHA1PRNG";
private static final String SHA1_ALGORITHM="SHA-1";
private final char[] buf;
private final Random random = new Random();
static {
for (int idx = 0; idx < 10; ++idx)
symbols[idx] = (char) ('0' + idx);
for (int idx = 10; idx < 36; ++idx)
symbols[idx] = (char) ('a' + idx - 10);
}

HMACKeyGeneratorServiceImpl() {
buf = new char[20];
}

public String generateAccessKey() {
for (int idx = 0; idx < buf.length; ++idx)
buf[idx] = symbols[random.nextInt(symbols.length)];
return new String(buf).toUpperCase();
}
public String generateHMACKey() throws GeneralSecurityException {
SecureRandom prng = SecureRandom.getInstance(SHA1PRNG_ALGORITHM);
String randomNum = new Integer(prng.nextInt()).toString();
MessageDigest sha = MessageDigest.getInstance(SHA1_ALGORITHM);
byte[] result = sha.digest(randomNum.getBytes());
return hexEncode(result);
}
private String hexEncode(byte[] aInput) {
StringBuilder result = new StringBuilder();
char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f' };
for (int idx = 0; idx < aInput.length; ++idx) {
byte b = aInput[idx];
result.append(digits[(b & 0xf0) >> 4]);
result.append(digits[b & 0x0f]);
}
return result.toString();
}
public String generateHMAC(String data, String hexEncodedKey) throws GeneralSecurityException,IOException {
String result = "";
byte[] keyBytes = hexEncodedKey.getBytes();
SecretKeySpec signingKey = new SecretKeySpec(keyBytes,HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
byte[] hexBytes = new Hex().encode(rawHmac);
result = new String(hexBytes, "UTF-8");
return result;
}


}

Client Side Signature Generation:

Considering the fact that client want to post some data to server, client must generate a request signature shared with him. The server will generate a similar request signature and match them.
The client and server must agree on a common plaintext generating algorithm to get the same hash. 

Lets assume that we will follow <param_name1>=<param_value1>;<param_name2>=<param_value2>;
so we will consider param name, param value paired using = and multiple parameters are combined using; as delimiter.


Authentication Flow

Client Side: 

Client want to post to server n params, param1 till param n with values value1 till value n.

Client generates a signature taking subset of these parameters name value pair.

  • Access Key : <client_access_key>
  • Secret Key: <client_secret_key>
  • Request Signature: <signature> = HMAC(<param_name1>=<param_value1>;<param_name2>=<param_value2>;, <client_secret_key>)


So client send a request to server with ACCESS_KEY = <client_access_key> and MESSAGE_HASH=<signature> in header  along with the parameters.

the client should populate the bean below and send a list so that server can identify the parameters used to generate the signature 


public class RestParameter implements Comparable<RestParameter>, Serializable{

private static final long serialVersionUID = -8654122030780643503L;
private String paramName;
private String paramValue;
private String order;

........
}

Server Side:

  • Server side signature generation involves fetching the ACCESS KEY from request header. 
  • Using the access key which is unique it fetches the SECRET KEY from database. 
  • Using the List of RestParameter and with the help of order of the parameters it generates the signature plain text.
  • Using the Secret key from step 2 and HMAC algorithm written earlier it generates a message hash.

Considering the fact that same plaintext and secret key is used to generate the hash both message hash from header that client sent and the server generated hash must be equal to proceed further.



[A full working code base is available here: https://github.com/badalb/spring-rest-security-tokenbased]




No comments:

Post a Comment