HMAC request validation

Not applicable

Hi,

Is it possible to use HMAC (or alternative hash) to validate the request has not been tampered with?

Some of our requests are based on the post payload so I would like to be able to hash the payload with a provided secret and confirm that within apigee when the request is received. Is this possible?

Thanks

Carl

Solved Solved
2 24 5,427
1 ACCEPTED SOLUTION

Not applicable

Implementation that uses - https://crypto.stanford.edu/sjcl/

string to Sign is defined at the top and can be changed based upon agreement between client and server. Variable extraction and key verification needs to be done prior to this script

function validDate(){
    context.setVariable("Auth.stringToSign", "Auth.dateSigned,Auth.apikey,Auth.nonce,Auth.salt,request.content");


    var validMilliseconds = 300000;
    var dtstr = context.getVariable("Auth.dateSigned"); // format yyyyMMddHHmmss UTC
    var yy = parseInt(dtstr.substr(0,4));
    var MM = parseInt(dtstr.substr(4,2))-1;
    var dd = parseInt(dtstr.substr(6,2));
    var HH = parseInt(dtstr.substr(8,2));
    var mm = parseInt(dtstr.substr(10,2));
    var ss = parseInt(dtstr.substr(12,2));


    var dt = new Date(Date.UTC(yy,MM,dd,HH,mm,ss));
    var dtnow = new Date();
    return ((Math.abs(dtnow - dt)) < validMilliseconds);
}


(function validate(validDate) {
    var stringToSign = "";
    var computedSig = "";
    var signatureMatch = false;


    if (validDate)
    {
        var contextVars = context.getVariable("Auth.stringToSign").split(",");


        var i = 0;
        for (i = 0; i < contextVars.length; i++) {
            var str = context.getVariable(contextVars[i]);
            if ( str !== null ) {
                stringToSign += str;
            }
        }


        var key = context.getVariable("verifyapikey.verifyApiKey.client_secret");
        var keyBits = sjcl.codec.utf8String.toBits(key);
        var out = (new sjcl.misc.hmac(keyBits, sjcl.hash.sha256)).mac(stringToSign);
        computedSig = sjcl.codec.hex.fromBits(out);
    }


    if (computedSig !== null && computedSig.length > 0)
    {
        var requestSig = context.getVariable("Auth.signature");
        // modified base64 i.e. + -> - , / -> _, remove trailing pad
        computedSig = computedSig.replace("+","-");
        computedSig = computedSig.replace("/","_");
        computedSig = computedSig.replace(/(=+$)/g, "");


        signatureMatch = (computedSig.valueOf() === requestSig.valueOf());
    }


    context.setVariable("hmacjs.signatureMatch", signatureMatch);


})(validDate());


View solution in original post

24 REPLIES 24

Not applicable

There is no built-in policy for HMAC validation, however if there is a Javascript/Node library that you could use, you may be able to do it that way. If you are using Apigee Edge, both Python and Java callouts are available as well.

Hi Michael,

Thanks for your reply. You mention a javascript library, is this something that exists is out in the community or are you referring to the fact it is possible to achieve this with a custom library?

Cheers

Carl

I meant to say that it is possible if a suitable javascript library exists for your purposes. A Google search of "HMAC javascript" gives some links to possible candidates but I'm not sure which or any would be of use to you.

Can you please elaborate on this more. If I were to use crypto-js or some other javascript library to do HMAC computation how would I import that library into my proxy such that it is callable from a javascript policy. In javaScript policy I can only specific one js file which is my file. How do I link other files / libraries there?

@Gagan Arora , Refer this http://apigee.com/docs/api-services/reference/javascript-policy#elements-includeurl to understand how you can add other Js files as dependent files .

Not applicable

@Carl Lambert, it will be great if you can update the thread on the library you ended up picking. I am curious, and am sure few others will be.

Not applicable

CryptoJS is a very usable library for this function (code.google.com/p/crypto-js).

The follow JSC snippet demonstrates who to validate a request payload:

var request = decodeURIComponent(context.getVariable("request.content"));
if(request) request = request.trim();

var signStartIndex = request.indexOf("&signature=");
var contentPayload = request.substring(5,signStartIndex);

contentPayload = contentPayload.replace(/\+/g,' ');

var  givenSign = request.substring( (signStartIndex + 11),request.length);
givenSign = givenSign.trim();

var hmacKey = context.getVariable("hmac.key");
if(hmacKey) hmacKey = hmacKey.trim();
var hashSign = CryptoJS.HmacSHA256(contentPayload,hmacKey );
var hashBase64Sign = CryptoJS.enc.Base64.stringify(hashSign);

var hmacValidation = false;
if(hashBase64Sign === givenSign){
	hmacValidation = true;
}

context.setVariable("hmac.validation", hmacValidation);

Note that this policy presumes that the decryption key was inserted into context as a variable "hmac.key". The result of this is a variable hmac.validation being set into context. You can then raise a fault in a later step if that is the desired result.

Signing just the payload still leaves the server vulnerable to replay attacks. As a result most implementations use current-time in the string-to-sign. By checking for current-time you can make a signature invalid after XX minutes. Also you should always HMAC using the secret, not the public key.

I have posted my implementation that uses the above and sjcl https://crypto.stanford.edu/sjcl/

Not applicable

Implementation that uses - https://crypto.stanford.edu/sjcl/

string to Sign is defined at the top and can be changed based upon agreement between client and server. Variable extraction and key verification needs to be done prior to this script

function validDate(){
    context.setVariable("Auth.stringToSign", "Auth.dateSigned,Auth.apikey,Auth.nonce,Auth.salt,request.content");


    var validMilliseconds = 300000;
    var dtstr = context.getVariable("Auth.dateSigned"); // format yyyyMMddHHmmss UTC
    var yy = parseInt(dtstr.substr(0,4));
    var MM = parseInt(dtstr.substr(4,2))-1;
    var dd = parseInt(dtstr.substr(6,2));
    var HH = parseInt(dtstr.substr(8,2));
    var mm = parseInt(dtstr.substr(10,2));
    var ss = parseInt(dtstr.substr(12,2));


    var dt = new Date(Date.UTC(yy,MM,dd,HH,mm,ss));
    var dtnow = new Date();
    return ((Math.abs(dtnow - dt)) < validMilliseconds);
}


(function validate(validDate) {
    var stringToSign = "";
    var computedSig = "";
    var signatureMatch = false;


    if (validDate)
    {
        var contextVars = context.getVariable("Auth.stringToSign").split(",");


        var i = 0;
        for (i = 0; i < contextVars.length; i++) {
            var str = context.getVariable(contextVars[i]);
            if ( str !== null ) {
                stringToSign += str;
            }
        }


        var key = context.getVariable("verifyapikey.verifyApiKey.client_secret");
        var keyBits = sjcl.codec.utf8String.toBits(key);
        var out = (new sjcl.misc.hmac(keyBits, sjcl.hash.sha256)).mac(stringToSign);
        computedSig = sjcl.codec.hex.fromBits(out);
    }


    if (computedSig !== null && computedSig.length > 0)
    {
        var requestSig = context.getVariable("Auth.signature");
        // modified base64 i.e. + -> - , / -> _, remove trailing pad
        computedSig = computedSig.replace("+","-");
        computedSig = computedSig.replace("/","_");
        computedSig = computedSig.replace(/(=+$)/g, "");


        signatureMatch = (computedSig.valueOf() === requestSig.valueOf());
    }


    context.setVariable("hmacjs.signatureMatch", signatureMatch);


})(validDate());


Sweet! And @Gagan Arora, you would be including that sjcl library like this, correct?:

<Javascript name='Javascript-verifyHmac' timeLimit='200' >
  <IncludeURL>jsc://sjcl.js</IncludeURL> 
  <ResourceURL>jsc://yourcodehere.js</ResourceURL>
</Javascript>

Yes. I used <IncludeURL> my policy looks like this:

<Javascript timeLimit="200" name="hmac"> 
  <DisplayName>validateHmac</DisplayName> 
  <ResourceURL>jsc://hmac.js</ResourceURL> 
  <IncludeURL>jsc://sjcl.js</IncludeURL> 
</Javascript>

besides the "sjcl.js", is there any other recommending library can be used for encrypting?

If you are talking about HMAC, then yes, there is a Java callout available here:

https://github.com/apigee/iloveapis2015-hmac-httpsignature

If that doesn't satisfy, then it sounds like you have a new question. If so, please POST a new question.

If you think it's appropriate, you can reference the URL to THIS question in your new question.

9473-ask-a-question.png

The variable "contextVars" is not a flow variable, but the code behind it try to get it from flow variable.It is really runnable?

var str = context.getVariable(contextVars[i]);

contextVars is set previously, by reading "Auth.stringToSign" .

(Yes it worked when I wrote it)

An update to an old question. There is no built-in policy for HMAC calculation.

However, there is a Java callout that does this. It will probably perform better than the Javascript-based option described in the other answers here. If you don't run at high concurrency, you probably don't care.

Find it here: https://github.com/apigee/iloveapis2015-hmac-httpsignature

We're working on adding an HMAC policy to Apigee. . . . .

Hi @Dino is there any ETA on that? And is it safe to use the JAVA callout you mentioned in the previous comment for now?


<p>Yes it's still safe, for now. That release is rolling out now, as we speak. I don't have an ETA on availability for your particular organization. Possibly this week. . . . </p>

thanks @Dino for the update. I wonder if any other policy is rolling out as well? I know this should be a separate question itself but if you can answer it here, please do. Otherwise, I will ask a separate question.

Yes, that's a separate question. I'll have a quick article, maybe a a screencast, on new features. Watch this space !

That would be great. Thanks.

Another update to an old question.

There IS a builtin policy to perform HMAC calculations.

See this answer.

BTW, the documentation for the builtin HMAC policy is available here:

https://cloud.google.com/apigee/docs/api-platform/reference/policies/hmac-policy