Skip to main content
  1. Posts/

API Authentication: Generating HMAC digest in PHP and Java

Table of Contents

User authentication is an important part of the web service API design. One of the common approach is the Hash-based Message Authentication Code – HMAC. Used together with transport level security it provides reliable mechanizm of user authentication and message integrity validation.

Imagine, we want to create java web service for our customers. Data encryption will be guaranteed by using https connection with TLS. We will implement API user authentication by using public API Key ID and a API Key Secret. User should generate an API Key: unique pair of Key ID and a Key Secret for his application. User should send that Key ID, message payload and a digest with every request. Digest is generated by signing all HTTP headers and message payload with Key Secret. (see Amazon’s recommendations)

In PHP there is a function hash_hmac for generating keyed hash value using the HMAC method. Here is the example:

<?php

$keyId = 'd36cb306-9341-466f-a794-d49fbc485d8b';
$payload = '{"command": "buy", "amount":10, currency":"EURUSD"}';
$secret = 'se1cr2et3w0r4d';

echo 'SHA-512 HMAC Digest: ', hash_hmac('sha512', $keyId . $payload, $secret);

Output:

SHA-512 HMAC Digest: 577a7927f55bc6ed1eaec08f7298e7c7596b6f951c4c6e8f24324fd9a1f0790adfdecbbd5ab73ad543fec7e6c3c23246a5dd8fae526e0b802ae99faccd06a29c

Call PHP function hash_algos to get a list of supported algorithms.

How to validate the digest in Java:

String apiKey = ... // X-KEY
byte[] secret = ... //
String rawPayload = ...
String receivedDigest = ... //
...

Mac digest = Mac.getInstance("HmacSHA512");
SecretKeySpec secretKey = new SecretKeySpec(secret, HMAC_SHA_512);

digest.init(secretKey);
digest.update(apiKey.getBytes(StandardCharsets.UTF_8));
digest.update(rawPayload.getBytes(StandardCharsets.UTF_8));
final byte[] expectedDigest = digest.doFinal();
digest.reset();

final byte[] receivedDigestBytes = DatatypeConverter.parseHexBinary(receivedDigest);
if (!MessageDigest.isEqual(receivedDigestBytes, expectedDigest)) {
    // invalid digest
}

References #