cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

[solved] HMAC authentication does not work

abo
Active Contributor
543

I need to talk to a server using REST with HMAC authentication. Before dealing with the server, I thought I would first write and validate the HMAC part and... that's exactly where I am stuck: the values I calculate in SAP are different from those I get using online calculators.

Here is my utility class:

 

CLASS zcl_auth_util DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    CONSTANTS c_algo_sha256 TYPE string VALUE `SHA256`.

    CLASS-METHODS build_key
      IMPORTING if_password TYPE string
                if_salt     TYPE string
      EXPORTING ef_key      TYPE xstring.

    CLASS-METHODS build_hmac_response
      IMPORTING if_key        TYPE xstring
                if_iterations TYPE i
                if_data       TYPE string
      EXPORTING ef_result     TYPE string
                ef_resultx    TYPE xstring
                ef_resultb    TYPE string.
ENDCLASS.



CLASS ZCL_AUTH_UTIL IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_AUTH_UTIL=>BUILD_HMAC_RESPONSE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IF_KEY                         TYPE        XSTRING
* | [--->] IF_ITERATIONS                  TYPE        I
* | [--->] IF_DATA                        TYPE        STRING
* | [<---] EF_RESULT                      TYPE        STRING
* | [<---] EF_RESULTX                     TYPE        XSTRING
* | [<---] EF_RESULTB                     TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD build_hmac_response.
    DATA lv_hmacxstring   TYPE xstring.
    DATA lv_hmacb64string TYPE string.

    DATA(lv_data) = if_data.
    DO if_iterations TIMES.
      TRY.
          cl_abap_hmac=>calculate_hmac_for_char( EXPORTING if_algorithm     = c_algo_sha256
                                                           if_key           = if_key
                                                           if_data          = lv_data
                                                 IMPORTING ef_hmacstring    = lv_data
                                                           ef_hmacxstring   = lv_hmacxstring
                                                           ef_hmacb64string = lv_hmacb64string ).
        CATCH cx_root INTO DATA(e_txt).
          WRITE / e_txt->get_text( ).
      ENDTRY.
    ENDDO.
    ef_result = lv_data.
    ef_resultx = lv_hmacxstring.
    ef_resultb = lv_hmacb64string.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_AUTH_UTIL=>BUILD_KEY
* +-------------------------------------------------------------------------------------------------+
* | [--->] IF_PASSWORD                    TYPE        STRING
* | [--->] IF_SALT                        TYPE        STRING
* | [<---] EF_KEY                         TYPE        XSTRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD build_key.
    DATA(lv_keyx) = cl_abap_hmac=>string_to_xstring( |{ if_password }{ if_salt }| ).

    TRY.
        DATA(lr_digest) = cl_abap_message_digest=>get_instance( if_algorithm = c_algo_sha256 ).
        lr_digest->update( if_data = lv_keyx ).
        lr_digest->digest( ).
        ef_key = cl_abap_hmac=>string_to_xstring( lr_digest->to_string( ) ).
      CATCH cx_abap_message_digest.  "
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

 

And this is the test class:

 

*"* use this source file for your ABAP unit test classes
CLASS ltc_auth_util_test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PUBLIC SECTION.
    METHODS _01_key_hash         FOR TESTING.
    METHODS _02_hmac_1_iter_no_sha FOR TESTING.
    METHODS _03_hmac_1_iter_with_sha FOR TESTING.

  PRIVATE SECTION.
    CLASS-METHODS class_setup.
    CLASS-METHODS class_teardown.

    METHODS setup.
    METHODS teardown.

    CONSTANTS c_password TYPE string VALUE `this_is_a_test_password`.
    CONSTANTS c_salt TYPE string VALUE `N1cbsxNB2X1uR0i20YmE9iV9L5icQtXQQ5V+7quww24VK32IStgnMhp/BIcgIoFH7FTUDaYHyZvsVrLRZRH4Nw==`.
    CONSTANTS c_pwd_salt_sha256 TYPE string VALUE `55557945A262E5FB6C553CBA833D0F75A11D42B17EEF27C7288B2A6C8B7CB930`.
    CONSTANTS c_testdata TYPE string VALUE `this is a test message to be signed`.
    CONSTANTS c_hmac_of_pwd_salt_testdata TYPE string VALUE `5B78CD8ADA9CABC8A65AAF5B0AD8648DD021FF1C285BEA51A3DDD6CC2D9D7A07`.

    DATA mo_cut TYPE REF TO zcl_auth_util.
ENDCLASS.


CLASS ltc_auth_util_test IMPLEMENTATION.
  METHOD _01_key_hash.
    DATA l_exp TYPE xstring.
    DATA l_act TYPE xstring.

    TRY.
        l_exp = cl_abap_hmac=>string_to_xstring( if_input = c_pwd_salt_sha256 ).
      CATCH cx_abap_message_digest.  "
    ENDTRY.

    mo_cut->build_key(
      EXPORTING if_password = c_password
                if_salt     = c_salt
      IMPORTING ef_key      = l_act ).
    cl_abap_unit_assert=>assert_equals( exp = l_exp
                                        act = l_act
                                        msg = `Hashed key (pwd/salt) does not match expected value` ).
  ENDMETHOD.

  METHOD _02_hmac_1_iter_no_sha.
    DATA l_act_string TYPE string.

    TRY.
        DATA(lv_keyx) = cl_abap_hmac=>string_to_xstring(
* This is the correct SHA256 of the password/salt from the first test
*       if_input = c_pwd_salt_sha256
* but first we try with raw password+salt
        if_input = |{ c_password }{ c_salt }| ).

      CATCH cx_abap_message_digest.  "
    ENDTRY.

    mo_cut->build_hmac_response( EXPORTING if_key        = lv_keyx
                                           if_iterations = 1
                                           if_data       = c_testdata
                                 IMPORTING ef_result     = l_act_string ).

    cl_abap_unit_assert=>assert_differs( exp = c_hmac_of_pwd_salt_testdata
                                        act = l_act_string
                                        msg = `HMAC with RAW password/salt matches hash, that's odd!` ).
  ENDMETHOD.

  METHOD _03_hmac_1_iter_with_sha.
    DATA l_act_string TYPE string.

    TRY.
        DATA(lv_keyx) = cl_abap_hmac=>string_to_xstring(
* This is the correct SHA256 of the password/salt from the first test
                            if_input = c_pwd_salt_sha256 ).
      CATCH cx_abap_message_digest.  "
    ENDTRY.

    mo_cut->build_hmac_response( EXPORTING if_key        = lv_keyx
                                           if_iterations = 1
                                           if_data       = c_testdata
                                 IMPORTING ef_result     = l_act_string ).

    cl_abap_unit_assert=>assert_equals( exp = c_hmac_of_pwd_salt_testdata
                                        act = l_act_string
                                        msg = 'HMAC with SHA256 of password/salt does not match expected hash' ).
  ENDMETHOD.


  METHOD class_setup.
  ENDMETHOD.

  METHOD class_teardown.
  ENDMETHOD.

  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.

  METHOD teardown.
    CLEAR mo_cut.
  ENDMETHOD.
ENDCLASS.

 

Test 1 and 2 pass, test 3 fails. Actually, test 2 was an afterthought: maybe the HMAC class is already doing a SHA256 on the key and I do not have to do it again? But no, apparently that is not the case and I am still clueless as to what might be the issue here.

Regardless of whether the remote server is expecting the key to be the SHA256 of password+salt or just of the password, it would be nice if my calculated values matched what the online calculators say 🙂

Any ideas? The code above is complete and runs ok in the latest Docker image for sure (but even at the office using a relatively ancient release, for that matter)

 

Accepted Solutions (1)

Accepted Solutions (1)

abo
Active Contributor
0 Kudos

Ok, at last I found out what was wrong:

  • for starters, we need the "calculate_hmac_for_raw" method
  • next, both this and the "calculate_hmac_for_char" method will perform SHA256 on the given key internally, it must NOT be done before!
  • last, if more than one iteration is required, the output should be manually copied over to the input: using the same variable is bad! (unit test for the win!)
  METHOD build_hmac_response.
    DATA lv_hmacstring    TYPE string.
    DATA lv_hmacb64string TYPE string.
    DATA lv_hmacxstring   TYPE xstring.

    DATA(lv_datax) = cl_abap_hmac=>string_to_xstring( if_input = if_data ).
    DO if_iterations TIMES.
      TRY.
          cl_abap_hmac=>calculate_hmac_for_raw( EXPORTING if_algorithm     = c_algo_sha256
                                                          if_key           = if_key
                                                          if_data          = lv_datax
                                                IMPORTING ef_hmacstring    = lv_hmacstring
                                                          ef_hmacxstring   = lv_hmacxstring
                                                          ef_hmacb64string = lv_hmacb64string ).
* Note: importing directly "lv_datax" clobbers the returns and will give the wrong result;
* to support the (yet untested) case of iterations > 1 just copy the string before looping!
          lv_datax = lv_hmacxstring.
        CATCH cx_abap_message_digest INTO DATA(e_txt).
          WRITE / e_txt->get_text( ).
      ENDTRY.
    ENDDO.
    ef_result = lv_hmacstring.
    ef_resultx = lv_hmacxstring.
    ef_resultb = lv_hmacb64string.

  ENDMETHOD.

And corresponding test method:

  METHOD _02_hmac_1_iter_no_sha.
    DATA l_act_string TYPE string.

    TRY.
* MEMO: we must NOT run SHA256 on the key, the HMAC method will take care of that!
        DATA(lv_keyx) = cl_abap_hmac=>string_to_xstring( if_input = |{ c_password }{ c_salt }| ).
      CATCH cx_abap_message_digest.
    ENDTRY.

    mo_cut->build_hmac_response( EXPORTING if_key        = lv_keyx
                                           if_iterations = 1
                                           if_data       = c_testdata
                                 IMPORTING ef_result     = l_act_string ).

    cl_abap_unit_assert=>assert_equals( exp = c_hmac_of_pwd_salt_testdata
                                        act = l_act_string
                                        msg = `HMAC with RAW password/salt does not match hash!` ).
  ENDMETHOD.

All is well 🙂

Answers (0)