Skip to Content
Technical Articles

HMAC-SHA1 hash verification on API Management

Introduction

MAC (Hash message authentication code) is a specific type of message authentication code involving a cryptographic hash function and a secret cryptographic key. Which can be used to simultaneously verify both the data integrity and the authenticity of a message. This blog post provides a mechanism on how to integrate such type of interfaces or webhooks into SAP Cloud system using SAP Cloud API Management.

Image result for hmac

Limitation: CPI inbound http adapter does not allow for a “no authorisation” policy nor does it have capability for other authorisation methods beside certificates/user-based authorisation but this can be resolved through API Management, which offers a great number of possibilities around policies.

Background

This blog post will illustrate the use case of API Management functioning as an intermediary between a source system, that triggers webhook notification secured with HMAC, and CPI, and getting in charge of the security between the systems. We use API management to authenticate the message and forward it to CPI then CPI does complex orchestration that is required to integrate into SAP Gigya and Marketing Cloud systems.

The objective of this blog post is to show how to verify HMAC-SHA1 Hash signature of sender system and generate and outbound call to CPI with user authentication.

 

API Policy Flow

The following diagram depicts the policy flow template that you need to create in SAP API Management to authenticate the message using HMAC-SHA1 and forward the message to SAP  CPI Layer for complex orchestration.

Step 01 – vmBasic Policy

Retrieve Target system user/passwords from secure value mapping and save it into a parameter that can then be used on Policy Flow.

<KeyValueMapOperations async="true" continueOnError="false" enabled="true" mapIdentifier="CPI" xmlns="http://www.sap.com/apimgmt">
<!-- The variable to which the retrieved value should be assigned. Use private for encripted key value map -->
    <Get assignTo="private.basicAuth.username">
        <Key>
            <Parameter>username</Parameter>
        </Key>
    </Get>
    <Get assignTo="private.basicAuth.password">
        <Key>
            <Parameter>password</Parameter>
        </Key>
    </Get>
    <Scope>environment</Scope>
</KeyValueMapOperations>

Webhook security 

Step 02 – extJson

Allows to extract Json payload or a particular node and save it into a variable.

<ExtractVariables async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <!-- the source variable which should be parsed -->
    <JSONPayload>
        <Variable name="jsonPayl">
            <JSONPath>$</JSONPath>
        </Variable>
    </JSONPayload>
    <Source clearPayload="false">request</Source>
    <VariablePrefix>myprefix</VariablePrefix>
</ExtractVariables>

Step 03 – vmPartner

Retrieve partner key from secure value mapping and save it into a parameter

<KeyValueMapOperations async="true" continueOnError="false" enabled="true" mapIdentifier="SourceSystem" xmlns="http://www.sap.com/apimgmt">
    <Get assignTo="private.partner">
        <Key>
            <Parameter>partner</Parameter>
        </Key>
    </Get>
    <Scope>environment</Scope>
</KeyValueMapOperations>

Step 04 – checkHash

Indicate the script name that will do the hash verification. In this case we used a Phyton script.

<Script async="false" continueOnError="false" enabled="true" timeLimit="200" xmlns='http://www.sap.com/apimgmt'>
    <!-- Resource URL refers to the main script file that should be run -->
    <ResourceURL>py://helper2.py</ResourceURL>
</Script>

Check the received hash string with our partner key. If they do not match it will raise an error, finalising the flow process.

import hashlib
import hmac
import base64
def make_digest(message, key):
    digester = hmac.new(base64.b64decode(key), message, hashlib.sha1)
    signature1 = digester.digest()
    signature2 = base64.b64encode(signature1)   
    return signature2;
message = "";
message = flow.getVariable("myprefix.jsonPayl");
webhookHash = flow.getVariable("request.header.<hashName>");
result = make_digest(str(message), flow.getVariable("private.partner"));
flow.setVariable("hash",result);
if (result != webhookHash):
    raise NameError("invalid origin")

CPI Security.

Step 05 – basicAuthentication

Set the basic authentication for the target system call, with the secured parameters defined on first step:

<BasicAuthentication async='true' continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>
    <!-- Operation can be Encode or Decode -->
    <Operation>Encode</Operation>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <!-- for Encode, User element can be used to dynamically populate the user value -->
    <User ref='private.basicAuth.username'></User>
    <!-- for Encode, Password element can be used to dynamically populate the password value -->
    <Password ref='private.basicAuth.password'></Password>
    <!-- Source is used to retrieve the encoded value of username and password. This should not be used if the operation is Encode-->
    <Source>request.header.Authorization</Source>
    <!-- Assign to is used to assign the encoded value of username and password to a variable. This should not be used if the operation is Decode -->
    <AssignTo createNew="false">request.header.Authorization</AssignTo>
</BasicAuthentication>

Test example on API Management

We can try the application directly on API Management by specifying the Webhook JSON payload and the security header hash.

API returns a successful response with the message set on CPI flow.

We can see on response headers SAP_MessageProcessingLogID of the message processed on SAP CPI.

In second test case we had altered the message data modifying the event id on the payload which invalidates HMAC signature.

API Management returns an error 500 to source system with the exception programmed on script validation step.

 

Conclusion:

API Managements offers a broad number of possibilities around security policies for API creation, which can also be used to set a bridge to more complex orchestration between systems and SAP Cloud Platform Integration, thus allowing to overcome CPI limitations regarding authorisation methods.
In this blog post we had explored one of such cases, using API Management to verify HMAC-SHA1 hash and act as a security handler between a source system and CPI.

2 Comments
You must be Logged on to comment or reply to a post.
  • Thanks Raquel Pereda for bringing one of my usecases to live in this blog. This kind of security at APIM level makes sense to have for sensitive data in API strategy.

    Cheers,

    Chandan

  • Hi Raquel, amazing content. Thank you!

     

    Considering “Step 04 – checkHash” is using String Comparison, it might be vulnerable to Timing Attack:

    https://sqreen.github.io/DevelopersSecurityBestPractices/timing-attack/python

     

    if (result != webhookHash):

     

    Trying to use “hmac.compare_digest” I had no success:

    https://docs.python.org/3/library/hmac.html#hmac.compare_digest

     

    So, I’ve changed the code to use Double HMAC comparison with Random Key as explained here in another Programming Language:

     

    https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy

     

    My final code was:

     

    import hashlib
    import hmac
    import base64
    import random
    import string
    
    def make_digest(secret, payload):
      hashBytes = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest();
      base64Hash = base64.b64encode(hashBytes);
      return base64Hash;
    
    message = "";
    message = flow.getVariable("request.content");
    
    webhookHash = flow.getVariable("request.header.X-DocuSign-Signature-1");
    resultHash = make_digest( flow.getVariable("private.connectKey"), str(message) );
    flow.setVariable("resultHash",resultHash);
    
    # Generate Random Key to Enforce Double HMAC Comparison avoiding Timing Attacks on String Comparison - by Pedro Baroni
    lettersAndDigits = string.ascii_letters + string.digits;
    randomKey = ''.join((random.choice(lettersAndDigits) for i in range(44)));
    
    flow.setVariable("randomKey",randomKey);
    doubleWebhookHash = make_digest( randomKey, webhookHash );
    doubleResultHash = make_digest( randomKey, resultHash );
    
    flow.setVariable("doubleWebhookHash",doubleWebhookHash);
    flow.setVariable("doubleResultHash",doubleResultHash);
    
    if (doubleResultHash != doubleWebhookHash):
        raise NameError("Invalid Origin! Different HMAC.")
    

     

    Best Regards.

     

    Pedro Baroni