BankgiroCentralen BGC HMAC SAP ABAP

License

This code is currently in beta.

Give it a testspin.

Idea for usage.

Store the key in a file at a USB stick with encryption.

Store the key at yet another USB stick with encryption as a backup.

Attach the USB stick to the pc.

Makue sure the files to be sent to the bank can be accessed from the pc being used to execute the sap gui.

Typically the files to be sent to the bank are written to the file system of the SAP server.

There are a number of options to access the files:

- ftp/sftp client to download the files from the SAP server.

- Access the files using common internet file system (CIFS)

- expose the fileshare at SAP server using samba if on unix or as a windows file share.

- let the sap server write the files to a windows file share (if on linux a file system mounted as cifs)

- use zapcmd, a norton comander clone for SAP to download the files

- use transaction CG3Y to download the file

Use the ZBGC_HMAC report to tamper protect (seal) the files.

Veryfy that the files are correct after adding the seal but before sending the files to the bank.

A VPN to the bank should be established, to send the files to the bank using a ftp client at the pc.

When done, store the USB sticks in a safe.

If you need an automated solution, consultants can help to customize to the specific requirements.

*&---------------------------------------------------------------------*
*& Report  ZBGC_HMAC
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*
REPORT  zbgc_hmac.
* Author: Otto Frost @ Nuport
TYPE-POOLS abap.
SELECTION-SCREEN COMMENT /1(50) comm1 MODIF ID mg1.
SELECTION-SCREEN ULINE.
SELECTION-SCREEN SKIP.
PARAMETERS p_inf TYPE file_name lower case.
PARAMETERS p_keyf TYPE file_name lower case.
PARAMETERS p_outf TYPE file_name lower case.
PARAMETERS p_test TYPE c AS CHECKBOX.
DATA i_filetable TYPE filetable.
DATA w_rc TYPE i.
DATA w_pathfd TYPE string.
DATA w_pathd TYPE string.
DATA w_filenamed TYPE string.
AT SELECTION-SCREEN OUTPUT.
  comm1 = 'BankGiroCentralen BGC HMAC'.
  LOOP AT SCREEN.
    IF screen-group1 = 'MG1'.
       screen-intensified = '1'.
      MODIFY SCREEN.
    ENDIF.
  ENDLOOP.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_inf.
  refresh i_filetable.
  CALL METHOD cl_gui_frontend_services=>file_open_dialog
*    EXPORTING
*      WINDOW_TITLE            =
*      DEFAULT_EXTENSION       =
*      DEFAULT_FILENAME        =
*      FILE_FILTER             =
*      WITH_ENCODING           =
*      INITIAL_DIRECTORY       =
*      MULTISELECTION          =
    CHANGING
      file_table              = i_filetable
      rc                      = w_rc
*      USER_ACTION             =
*      FILE_ENCODING           =
*    EXCEPTIONS
*      FILE_OPEN_DIALOG_FAILED = 1
*      CNTL_ERROR              = 2
*      ERROR_NO_GUI            = 3
*      NOT_SUPPORTED_BY_GUI    = 4
*      others                  = 5
          .
  IF sy-subrc <> 0.
*   MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*              WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
  ENDIF.
  READ TABLE i_filetable INDEX 1 INTO p_inf.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_keyf.
  refresh i_filetable.
  CALL METHOD cl_gui_frontend_services=>file_open_dialog
*    EXPORTING
*      WINDOW_TITLE            =
*      DEFAULT_EXTENSION       =
*      DEFAULT_FILENAME        =
*      FILE_FILTER             =
*      WITH_ENCODING           =
*      INITIAL_DIRECTORY       =
*      MULTISELECTION          =
    CHANGING
      file_table              = i_filetable
      rc                      = w_rc
*      USER_ACTION             =
*      FILE_ENCODING           =
*    EXCEPTIONS
*      FILE_OPEN_DIALOG_FAILED = 1
*      CNTL_ERROR              = 2
*      ERROR_NO_GUI            = 3
*      NOT_SUPPORTED_BY_GUI    = 4
*      others                  = 5
          .
  IF sy-subrc <> 0.
*   MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*              WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
  ENDIF.
  READ TABLE i_filetable INDEX 1 INTO p_keyf.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_outf.
  w_pathfd = p_outf.
  CALL METHOD cl_gui_frontend_services=>file_save_dialog
*  EXPORTING
*    WINDOW_TITLE         =
*    DEFAULT_EXTENSION    =
*    DEFAULT_FILE_NAME    =
*    WITH_ENCODING        =
*    FILE_FILTER          =
*    INITIAL_DIRECTORY    =
*    PROMPT_ON_OVERWRITE  = 'X'
    CHANGING
      filename             = w_filenamed
      path                 = w_pathd
      fullpath             = w_pathfd
*      USER_ACTION          = W_USER_ACTION
*    FILE_ENCODING        =
*  EXCEPTIONS
*    CNTL_ERROR           = 1
*    ERROR_NO_GUI         = 2
*    NOT_SUPPORTED_BY_GUI = 3
*    others               = 4
          .
  IF sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*            WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
  ENDIF.
  p_outf = w_pathfd.
INITIALIZATION.
START-OF-SELECTION.
  IF p_test = abap_true.
    PERFORM test.
  ELSE.
    PERFORM bgc_hmac.
  ENDIF.
*----------------------------------------------------------------------*
*       CLASS zcl_bgc_hmac DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS zcl_bgc_hmac DEFINITION.
  PUBLIC SECTION.
    METHODS test.
    METHODS createsignature IMPORTING
      value(keyfile) TYPE string
      value(infile) TYPE string
      value(outfile) TYPE string
      value(date) TYPE string
      value(ls) TYPE string.
  PROTECTED SECTION.
*  package section.
  PRIVATE SECTION.
    TYPES t_linex(512) TYPE x.
    CONSTANTS co_empty TYPE string VALUE ''.
    METHODS normalize IMPORTING value(s) TYPE string RETURNING value(r) TYPE string.
    METHODS bgc_hmac IMPORTING value(indata) TYPE string value(keyhex) TYPE string RETURNING value(r) TYPE string.
    METHODS sealopeningrecord IMPORTING value(date) TYPE string RETURNING value(r) TYPE string.
    METHODS tamperprotectionrecord IMPORTING value(kvv) TYPE string value(mac) TYPE string value(date) TYPE string RETURNING value(r) TYPE string.
    METHODS xtab2xstring IMPORTING value(tab) TYPE ANY TABLE value(length) TYPE i RETURNING value(r) TYPE xstring.
ENDCLASS.                    "zcl_bgc_hmac DEFINITION
*----------------------------------------------------------------------*
*       CLASS zcl_bgc_hmac IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS zcl_bgc_hmac IMPLEMENTATION.
  METHOD test.
    DATA indata TYPE string.
    DATA key TYPE string VALUE '1234567890ABCDEF1234567890ABCDEF'.
    DATA mac TYPE string.
    DATA l_s TYPE string.
    DATA l_normalized TYPE string.
    DATA l_space TYPE string.
    CONCATENATE space space INTO l_space SEPARATED BY space.
    indata = 'AAAÅÄÖÜåäöü'.
    indata = me->normalize( indata ).
**********
* Test 1A
**********
    mac = me->bgc_hmac( indata = '00000000' keyhex = key ).
    IF  to_upper( mac )  =  'FF365893D899291C3BF505FB3175E880'   .
      WRITE:/ 'Test 1A ok' .
    ELSE .
      WRITE:/ 'Test 1A err' .
      l_s = to_upper( mac ).
      WRITE:/ l_s.
      WRITE:/ 'FF365893D899291C3BF505FB3175E880'.
    ENDIF.
**********
* Test 1B
**********
    indata = '00000' && cl_abap_char_utilities=>cr_lf &&
    '0000' && cl_abap_char_utilities=>cr_lf &&
    '000' && cl_abap_char_utilities=>cr_lf &&
    '00' && cl_abap_char_utilities=>cr_lf && '0' && cl_abap_char_utilities=>cr_lf.
    l_normalized = me->normalize( indata ).
*    WRITE:/ '1B', space, indata.
*    WRITE:/ '1B', space, l_normalized.
    mac = me->bgc_hmac( indata = l_normalized keyhex = key ).
    IF to_upper( mac ) = '9BA94363739D45256DF4B6FA3B9DE1CD'.
      WRITE:/ 'Test 1B ok'.
    ELSE.
      WRITE:/ 'Test 1B err'.
    ENDIF.
**********
* Test 1C
**********
    indata =
      '110009912346071215LEVERANTZRSBETALNINGAR                                       2'
        && cl_abap_char_utilities=>cr_lf
        && '10000000C0000000000000000000000000000000000000000000000000000000000000002'
        && cl_abap_char_utilities=>cr_lf
        && '10000000C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002'
        && cl_abap_char_utilities=>cr_lf
        && '10000000C002'
        && cl_abap_char_utilities=>cr_lf.
    l_normalized = me->normalize( indata ).
*    WRITE:/ '1C', space, indata.
*    WRITE:/ '1C', space, l_normalized.
    mac = me->bgc_hmac( indata = l_normalized keyhex = key ).
    IF to_upper( mac ) = '826CA6CBA33F7E1D3CC9161A0956A35B'.
      WRITE:/ 'Test 1C ok'.
    ELSE.
      WRITE:/ 'Test 1C err'.
    ENDIF.
**********
* Test 2A
**********
    indata = l_space && '!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~' && cl_abap_char_utilities=>cr_lf.
    l_normalized = me->normalize( indata ).
*    WRITE:/ '2A', space, indata.
*    WRITE:/ '2A', space, l_normalized.
*    DATA x TYPE string.
*    x =  l_space &&   '!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'.
*    IF x = l_normalized.
*      WRITE:/ '2A normalized ok'.
*    ELSE.
*      WRITE:/ '2A normalized not ok'.
*    ENDIF.
    mac = me->bgc_hmac( indata = l_normalized keyhex = key ).
*    WRITE:/ mac.
    IF to_upper( mac ) = '9473EDFCAA8CD2434D6D76ABFFC991BD'.
      WRITE:/ 'Test 2A ok'.
    ELSE.
      WRITE:/ 'Test 2A err'.
    ENDIF.
**********
* Test 2B
**********
    indata = 'õÕ^~@`!"#$%&''(' &&  cl_abap_char_utilities=>horizontal_tab  && ')*+,-./§½' && cl_abap_char_utilities=>cr_lf.
    l_normalized = me->normalize( indata ).
*     WRITE:/ '2B', space, indata.
*     WRITE:/ '2B', space, l_normalized.
*    DATA x TYPE string.
*    x =  l_space &&   '!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'.
*    IF x = l_normalized.
*      WRITE:/ '2A normalized ok'.
*    ELSE.
*      WRITE:/ '2A normalized not ok'.
*    ENDIF.
    mac = me->bgc_hmac( indata = l_normalized keyhex = key ).
    IF mac = to_upper( '20956E44B404C4085446139B2B952D77' ).
      WRITE:/ 'Test 2B ok'.
    ELSE.
      WRITE:/ 'Test 2B err'.
    ENDIF.
**********
* Test 2C
**********
    indata = 'ÄÖÅäöå' && cl_abap_char_utilities=>cr_lf.
    l_normalized = me->normalize( indata ).
    mac = me->bgc_hmac( indata = l_normalized keyhex = key ).
    IF mac = to_upper( '515704694958361678194D51850FF157' ).
      WRITE:/ 'Test 2C ok'.
    ELSE.
      WRITE:/ 'Test 2C err'.
    ENDIF.
  ENDMETHOD.                    "test
  METHOD normalize.
    DATA l_s TYPE string.
    DATA l_notallowedchars TYPE string.
    DATA cc  TYPE REF TO cl_abap_conv_in_ce.
    DATA l_repl_char     TYPE char1.
    DATA l_external_codepage TYPE string VALUE 'ISO-8859-1'.
    DATA l_sap_codepage TYPE cpcodepage.
    DATA l_abap_encoding TYPE abap_encoding.
    DATA l_space TYPE string.
    CONCATENATE space space INTO l_space SEPARATED BY space .
    CALL FUNCTION 'SCP_CODEPAGE_BY_EXTERNAL_NAME'
      EXPORTING
        external_name       = l_external_codepage
*    KIND                = 'H'
      IMPORTING
        sap_codepage        = l_sap_codepage
*  EXCEPTIONS
*    NOT_FOUND           = 1
*    OTHERS              = 2
              .
    IF sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
    ENDIF.
    l_abap_encoding = l_sap_codepage.
    l_s = s.
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+0(1)   IN l_s WITH co_empty. " remove cr
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+1(1)   IN l_s WITH co_empty. " remove lf
*  l_s = me->replace( s = l_s find = 'C9' repl = '40' ). " É --> @
*  l_s = me->replace( s = l_s find = 'C5' repl = '5D' ). " Å --> ]
*  l_s = me->replace( s = l_s find = 'D6' repl = '5C' ). " Ö --> \
*  l_s = me->replace( s = l_s find = 'C4' repl = '5B' ). " Ä --> [
*  l_s = me->replace( s = l_s find = 'DC' repl = '5E' ). " Ü --> ^
*  l_s = me->replace( s = l_s find = 'E9' repl = '60' ). " é --> `
*  l_s = me->replace( s = l_s find = 'E4' repl = '7B' ). " ä --> {
*  l_s = me->replace( s = l_s find = 'F6' repl = '7C' ). " ö --> |
*  l_s = me->replace( s = l_s find = 'E4' repl = '7D' ). " å --> }
*  l_s = me->replace( s = l_s find = 'FC' repl = '7E' ). " ü --> ~
    REPLACE ALL OCCURRENCES OF 'É'   IN l_s WITH '@'.       " C9 40
    REPLACE ALL OCCURRENCES OF 'Å'   IN l_s WITH ']'.       " C5 5D
    REPLACE ALL OCCURRENCES OF 'Ö'   IN l_s WITH '\'.       " D6 5C
    REPLACE ALL OCCURRENCES OF 'Ä'   IN l_s WITH '['.       " C4 5B
    REPLACE ALL OCCURRENCES OF 'Ü'   IN l_s WITH '^'.       " DC 5E
    REPLACE ALL OCCURRENCES OF 'é'   IN l_s WITH '`'.       " E9 60
    REPLACE ALL OCCURRENCES OF 'ä'   IN l_s WITH '{'.       " E4 7B
    REPLACE ALL OCCURRENCES OF 'ö'   IN l_s WITH '|'.       " F6 7C
    REPLACE ALL OCCURRENCES OF 'å'   IN l_s WITH '}'.       " E4 7D
    REPLACE ALL OCCURRENCES OF 'ü'   IN l_s WITH '~'.       " FC 7E
    l_notallowedchars = '[' &&      " // start of range
      '^' &&                        " // not
      l_space &&                        " // space
      '\!' &&                       " // exclamation mark
      '\"' &&                       " // double quote
      '\#' &&                        " // hash
      '\$' &&                       " // dollar
      '\%' &&                       " // percent
      '\&' &&                       " // ampersand
      '\''' &&                      " // single quote
      '\(' &&                       " // left parenthesis
      '\)' &&                       " // right parenthesis
      '\*' &&                       " // star
      '\+' &&                       " // plus
      '\,' &&                       " // comma
      '\-' &&                       " // minus
      '\.' &&                       " // dot
      '\/' &&                       " // slash
      '0-9' &&                      " // range 0-9
      '\:' &&                       " // colon
      '\;' &&                       " // semicolon
      '\<' &&                       " // less than
      '\=' &&                       " // equals
      '\>' &&                       " // greater than
      '\?' &&                       " // question mark
      '\@' &&                       " // at
      'É' &&                        " // E with apostrophe
      'A-Z' &&                      " // range A-Z
      '\[' &&                       " // left bracket
      '\\' &&                       " // back slash
      '\]' &&                       " // right bracket
      '\^' &&                       " // caret
      '_' &&                        " // under score
      '\`' &&                       " // back quote
      'a-z' &&                      " // range a-z
      '\{' &&                       " // left curly brace
      '\|' &&                       " // vertical bar
      '\}' &&                       " // right curly brace
      '\~' &&                       " // tilde
      ']'.                         " // end of range
    cc = cl_abap_conv_in_ce=>create( input = 'C3' encoding = l_abap_encoding ).
    cc->read( IMPORTING data = l_repl_char  ).
*    WRITE:/ l_notallowedchars.
*    WRITE:/ 's ' , space, l_s.
    REPLACE ALL OCCURRENCES OF REGEX l_notallowedchars IN l_s WITH l_repl_char.
*    WRITE:/ 's ', space, l_s.
    r = l_s.
  ENDMETHOD.                    "normalize
  METHOD bgc_hmac.
    DATA lw_alg TYPE string VALUE 'SHA256'.                 " sha1
    DATA lw_key TYPE xstring.
    DATA l_x TYPE xstring.
    DATA l_conv_out TYPE REF TO cl_abap_conv_out_ce.
    DATA l_external_codepage TYPE string VALUE 'ISO-8859-1'.
    DATA l_sap_codepage TYPE cpcodepage.
    DATA l_abap_encoding TYPE abap_encoding.
    CALL FUNCTION 'SCP_CODEPAGE_BY_EXTERNAL_NAME'
      EXPORTING
        external_name = l_external_codepage
*       KIND          = 'H'
      IMPORTING
        sap_codepage  = l_sap_codepage
      EXCEPTIONS
        not_found     = 1
        OTHERS        = 2.
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.
    l_abap_encoding = l_sap_codepage.
    lw_key = keyhex.
    l_conv_out = cl_abap_conv_out_ce=>create( encoding = l_abap_encoding ).
    l_conv_out->write( data = indata ).
    l_x = l_conv_out->get_buffer( ).
    cl_abap_hmac=>calculate_hmac_for_raw( EXPORTING if_algorithm = lw_alg if_key = lw_key if_data = l_x
                                          IMPORTING ef_hmacstring = r ).
    r = r+0(32).
  ENDMETHOD.                    "bgc_hmac
  METHOD sealopeningrecord.
    data l_space type string.
    concatenate space space into l_space separated by space.
    r = '00' && date && 'HMAC'.
    WHILE ( strlen( r ) < 80 ).
      r = r && l_space.
    ENDWHILE.
  ENDMETHOD.                    "sealopeningrecord
  METHOD tamperprotectionrecord.
    data l_space type string.
    concatenate space space into l_space separated by space.
    r = '99' && date && kvv && mac.
    WHILE ( strlen( r ) < 80 ).
      r = r && l_space.
    ENDWHILE.
  ENDMETHOD.                    "tamperprotectionrecord
  METHOD createsignature.
    DATA l_date TYPE string.
    DATA l_length TYPE i.
    DATA li_data TYPE STANDARD TABLE OF t_linex.
    DATA l_linex TYPE t_linex.
    DATA l_x TYPE xstring.
    DATA l_s TYPE string.
    DATA l_bytes TYPE i.
    DATA l_conv_in TYPE REF TO cl_abap_conv_in_ce.
    DATA l_conv_out TYPE REF TO cl_abap_conv_out_ce.
    DATA l_key TYPE string.
    DATA l_keyx TYPE xstring.
    DATA l_external_codepage TYPE string VALUE 'ISO-8859-1'.
    DATA l_sap_codepage TYPE cpcodepage.
    DATA l_abap_encoding TYPE abap_encoding.
    DATA l_data TYPE string.
    DATA l_filedata TYPE string.
    DATA l_line_separator_exists TYPE boolean VALUE abap_false.
    DATA l_filedata_normalized TYPE string.
    DATA l_mac TYPE string.
    DATA l_kvv TYPE string.
    DATA l_tamperprotectionrecord TYPE string.
    DATA l_line_separator TYPE string.
    IF date IS INITIAL.
      l_date = sy-datum+2(6).
    ELSE.
      l_date = date.
    ENDIF.
    REFRESH li_data.
    CLEAR l_length.
    cl_gui_frontend_services=>gui_upload(
      EXPORTING
        filename = keyfile
        filetype = 'BIN'
        has_field_separator = space
        read_by_line = abap_false
      IMPORTING
        filelength = l_length
      CHANGING
        data_tab = li_data ).
    l_x = me->xtab2xstring( tab = li_data length = l_length ).
    l_conv_in  = cl_abap_conv_in_ce=>create(
          encoding = 'UTF-8' input = l_x ).
* the key is hexencoded
* so remove any cr/lf
    l_conv_in->read( IMPORTING data = l_s ).
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+0(1)   IN l_s WITH co_empty. " remove cr
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+1(1)   IN l_s WITH co_empty. " remove lf
    l_key = l_s.
    l_keyx = l_key.
    REFRESH li_data.
    CLEAR l_length.
    cl_gui_frontend_services=>gui_upload(
      EXPORTING
        filename = infile
        filetype = 'BIN'
        has_field_separator = space
        read_by_line = abap_false
      IMPORTING
        filelength = l_length
      CHANGING
        data_tab = li_data ).
    l_x = me->xtab2xstring( tab = li_data length = l_length ).
    CALL FUNCTION 'SCP_CODEPAGE_BY_EXTERNAL_NAME'
      EXPORTING
        external_name = l_external_codepage
*       KIND          = 'H'
      IMPORTING
        sap_codepage  = l_sap_codepage
      EXCEPTIONS
        not_found     = 1
        OTHERS        = 2.
    IF sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
    ENDIF.
    l_abap_encoding = l_sap_codepage.
    l_conv_in  = cl_abap_conv_in_ce=>create(
          encoding = l_abap_encoding input = l_x ).
    l_conv_in->read( IMPORTING data = l_s ).
    l_data = l_s.
    l_filedata = me->sealopeningrecord( date = l_date ) && ls && l_data.
    IF ( substring( val = l_filedata off = ( strlen( l_filedata ) - 1 )  len = 1  ) = cl_abap_char_utilities=>cr_lf+0(1) )
    OR ( substring( val = l_filedata off = ( strlen( l_filedata ) - 1 ) len = 1  ) = cl_abap_char_utilities=>cr_lf+1(1) ).
      l_line_separator_exists = abap_true.
    ENDIF.
    l_filedata_normalized = me->normalize( l_filedata ).
*    l_conv_out = cl_abap_conv_out_ce=>create( encoding = l_abap_encoding ).
*    l_conv_out->write( data = l_filedata_normalized ).
*    l_x = l_conv_out->get_buffer( ).
    l_mac = me->bgc_hmac( indata = l_filedata_normalized keyhex = l_key ).
    l_kvv = me->bgc_hmac( indata = '00000000' keyhex = l_key ).
    l_tamperprotectionrecord = me->tamperprotectionrecord( kvv = l_kvv mac = l_mac date = l_date ).
    IF l_line_separator_exists = abap_false.
      l_line_separator = cl_abap_char_utilities=>cr_lf.
    ENDIF.
    l_filedata = l_filedata && l_line_separator && l_tamperprotectionrecord && ls.
* make sure to have cr/lf as line separator this is what bgc expects
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+0(1)   IN l_filedata WITH co_empty. " remove all cr
    REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+1(1)   IN l_filedata WITH cl_abap_char_utilities=>cr_lf. " add cr in front of lf
    l_conv_out = cl_abap_conv_out_ce=>create( encoding = l_abap_encoding ).
    l_conv_out->write( data = l_filedata ).
    l_x = l_conv_out->get_buffer( ).
    CALL FUNCTION 'LXE_COMMON_XSTRING_FILE_EXPORT'
      EXPORTING
        xstring  = l_x
        filename = outfile.
*
*CALL METHOD cl_gui_frontend_services=>gui_download
*  EXPORTING
*     bin_filesize              =
*     filename                  =
*     filetype                  = 'BIN'
**    append                    = SPACE
**    write_field_separator     = SPACE
**    header                    = '00'
**    trunc_trailing_blanks     = SPACE
**    write_lf                  = 'X'
**    col_select                = SPACE
**    col_select_mask           = SPACE
**    dat_mode                  = SPACE
**    confirm_overwrite         = SPACE
**    no_auth_check             = SPACE
**    codepage                  = SPACE
**    ignore_cerr               = ABAP_TRUE
**    replacement               = '#'
**    write_bom                 = SPACE
**    trunc_trailing_blanks_eol = 'X'
**    wk1_n_format              = SPACE
**    wk1_n_size                = SPACE
**    wk1_t_format              = SPACE
**    wk1_t_size                = SPACE
**    show_transfer_status      = 'X'
**    fieldnames                =
**    write_lf_after_last_line  = 'X'
**  IMPORTING
**    filelength                =
*  changing
*    data_tab                  =
**  EXCEPTIONS
**    file_write_error          = 1
**    no_batch                  = 2
**    gui_refuse_filetransfer   = 3
**    invalid_type              = 4
**    no_authority              = 5
**    unknown_error             = 6
**    header_not_allowed        = 7
**    separator_not_allowed     = 8
**    filesize_not_allowed      = 9
**    header_too_long           = 10
**    dp_error_create           = 11
**    dp_error_send             = 12
**    dp_error_write            = 13
**    unknown_dp_error          = 14
**    access_denied             = 15
**    dp_out_of_memory          = 16
**    disk_full                 = 17
**    dp_timeout                = 18
**    file_not_found            = 19
**    dataprovider_exception    = 20
**    control_flush_error       = 21
**    not_supported_by_gui      = 22
**    error_no_gui              = 23
**    others                    = 24
*        .
*IF sy-subrc <> 0.
** MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
**            WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
*ENDIF.
*
  ENDMETHOD.                     "tamperprotectionrecord
  METHOD xtab2xstring.
    DATA l_linex TYPE t_linex.
    DATA l_x TYPE xstring.
    DATA l_bytes TYPE i.
    DATA l_length TYPE i.
    l_length = length.
    LOOP AT tab INTO l_linex.
      IF l_length > 512.
        l_bytes = 512.
      ELSE.
        l_bytes = l_length.
      ENDIF.
      CONCATENATE l_x l_linex(l_bytes) INTO l_x IN BYTE MODE.
      l_length = l_length - 512.
    ENDLOOP.
    r = l_x.
  ENDMETHOD.                    "xtab2xstring
ENDCLASS.                    "zcl_bgc_hmac IMPLEMENTATION
*&---------------------------------------------------------------------*
*&      Form  test
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM test.
  DATA: lo_hmac TYPE REF TO cl_abap_hmac.
  DATA: lf_hmac_string TYPE string.
  DATA: lw_alg TYPE string VALUE 'sha256'.                  " sha1
  DATA: lw_key TYPE xstring VALUE 'AAAA'.
  lo_hmac = cl_abap_hmac=>get_instance( if_algorithm = 'sha256' if_key = lw_key ).
  " update HMAC with input
  lo_hmac->update( if_data = '010203' ).
  " finalise hmac
  lo_hmac->final( ).
  " String
  lf_hmac_string   = lo_hmac->to_string( ).
*  WRITE:/ lf_hmac_string .
  " Base64 representation
  lf_hmac_string  = lo_hmac->to_base64( ).
*  WRITE:/ lf_hmac_string .
  DATA l_bgc_hmac TYPE REF TO zcl_bgc_hmac.
  CREATE OBJECT l_bgc_hmac.
  l_bgc_hmac->test( ).
ENDFORM.                    "test
*&---------------------------------------------------------------------*
*&      Form  bgc_hmac
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM bgc_hmac.
  DATA l_bgc_hmac TYPE REF TO zcl_bgc_hmac.
  DATA l_keyfile TYPE string.
  DATA l_outfile TYPE string.
  DATA l_infile TYPE string.
  DATA l_date TYPE string.
  DATA l_ls TYPE string.
  l_keyfile = p_keyf.
  l_infile = p_inf.
  l_outfile = p_outf.
  l_date = sy-datum+2(6).
  l_ls = cl_abap_char_utilities=>cr_lf.
  CREATE OBJECT l_bgc_hmac.
  l_bgc_hmac->createsignature( keyfile = l_keyfile  infile = l_infile outfile = l_outfile date = l_date ls = l_ls ).
ENDFORM.                    "bgc_hmac
*  methods replace importing value(s) type string value(find) type xstring value(repl) type xstring returning value(r) type string.
*method replace.
*  data l_s type string.
*  DATA cc  TYPE REF TO cl_abap_conv_in_ce.
*  DATA l_find_char     TYPE char1.
*  DATA l_repl_char     TYPE char1.
*  l_s = s.
*  cc = cl_abap_conv_in_ce=>create( input = find encoding = 'UTF-8' ).
*  cc->read( IMPORTING data = l_find_char  ).
*  cc = cl_abap_conv_in_ce=>create( input = repl encoding = 'UTF-8' ).
*  cc->read( IMPORTING data = l_repl_char  ).
*  REPLACE ALL OCCURRENCES OF l_find_char IN l_s WITH l_repl_char.
*  r = l_s.
*endmethod.