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.
HMAC JAVA Code :
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