Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

SAP ABAP: TOTP Generation (Time-based one-time password algorithm)

former_member202253
Participant
0 Kudos

Dear Guru's,

I hope you are well.

We have requirement to generate TOTP using ABAP and I got stuck on the requirement to generate TOTP tokens from SAP ABAP. This is very easy and doable in JAVA but challenging in ABAP.

Based on my analysis this can be doable using Class: CL_ABAP_HMAC.

I am trying to convert Java Code to ABAP but I am stuck on some of the places where byte Array in Java Code and not able to make it result like java code.

Also I have gone through : https://datatracker.ietf.org/doc/html/rfc6238 and based on this I was trying code for JAVA and ABAP.

I am attaching both codes for your reference.

It would be helpful if you can advise. How can I get results like in the Java class?

Thank you

ABAP Code:

<br>DATA: im_date TYPE sy-datum.<br>DATA: im_time       TYPE sy-uzeit,<br>      im_msec       TYPE numc7,<br>      l_xstring_key TYPE xstring,<br>      l_key         TYPE string,<br>      ex_timestamp  TYPE string.<br><br><br>DATA: l_date TYPE sy-datum.<br><br>DATA: l_days_timestamp TYPE timestampl.<br><br>DATA: l_secs_timestamp TYPE timestampl.<br><br>DATA: l_i_days TYPE i.<br><br>DATA: l_i_sec TYPE i.<br><br>DATA: l_timestamp TYPE timestampl.<br><br>DATA: l_dummy TYPE string.<br><br>DATA: ts2(25) TYPE c,<br>      ts      TYPE timestampl.<br>GET TIME STAMP FIELD ts.<br>DATA: dat TYPE date,<br>      tim TYPE time,<br>      tz  TYPE timezone.<br>MOVE ts TO ts2.<br>CONDENSE ts2.<br>DATA: t1(10),t2(13) TYPE c.<br>CONVERT TIME STAMP ts TIME ZONE sy-zonlo INTO DATE dat TIME tim.<br>CONCATENATE dat+0(4) dat+4(2) dat+6(2) INTO t1.<br><br>im_date =  dat.<br><br>CONCATENATE tim+0(2)  tim+2(2)  tim+4(2)   INTO t2.<br>im_time = tim.<br>im_msec = ts2+15(7).<br><br><br><br>*-----------------------------------------------------------------------<br><br>* milliseconds for the days since January 1, 1970, 00:00:00 GMT<br><br>* one day has 86400 seconds<br><br>l_date = '19700101'.<br><br>l_i_days = im_date - l_date.<br><br>* timestamp for days past in seconds<br><br>l_days_timestamp = l_i_days * 86400.<br><br>l_i_sec = im_time.<br><br>* timestamp for time at present day<br><br>l_secs_timestamp = l_i_sec.<br><br>l_timestamp = ( l_days_timestamp + l_secs_timestamp ) * 1000.<br><br>ex_timestamp = l_timestamp.<br><br>SPLIT ex_timestamp AT '.' INTO ex_timestamp l_dummy.<br><br>ex_timestamp = ex_timestamp + im_msec.<br><br>SHIFT ex_timestamp RIGHT DELETING TRAILING space.<br><br>SHIFT ex_timestamp LEFT DELETING LEADING space.<br>DATA(l) = ex_timestamp / 1000.<br><br>ex_timestamp = round( val = ( ( round( val = l  dec = 0 ) - 0 ) / 30 ) dec = 0 ).<br><br><br><br><br>TYPES t_long_int(16) TYPE p DECIMALS 0.<br>DATA: number TYPE t_long_int,<br>      result TYPE string.<br><br>number = ex_timestamp.<br>PERFORM convert_base10_to_base16 USING    number<br>                                 CHANGING result.<br>WRITE: / number, '-->', result.<br><br><br><br>TRY.<br>    l_key  = '3132333435363738393031323334353637383930'.<br>    l_xstring_key  = cl_abap_hmac=>string_to_xstring( l_key ).<br>  CATCH cx_abap_message_digest.<br><br>ENDTRY.<br><br>clear : result.<br>number = ex_timestamp.<br>PERFORM  gettotp USING    number<br>                          l_xstring_key<br>                          CHANGING result.<br><br>  write /       result.<br><br>FORM gettotp USING    timeindex TYPE t_long_int<br>                      key TYPE xstring<br>                     CHANGING result TYPE string.<br><br><br>*TRY.<br>CALL METHOD cl_abap_hmac=>get_instance<br>  EXPORTING<br>    if_algorithm = 'SHA1'<br>    if_key       = key<br>  receiving<br>    ro_object    = DATA(lo_hmac)<br>    .<br>* CATCH cx_abap_message_digest .<br>*ENDTRY.<br><br><br><br><br>data(l_string) = CONV string( timeindex ).<br>  TRY.<br>      CALL METHOD lo_hmac->final<br>        EXPORTING<br>          if_data = cl_abap_hmac=>string_to_xstring( l_string  )<br>      if_offset        = 0<br>      if_length        = strlen( l_string )<br>    IMPORTING<br>      ef_hmacstring    = result<br>*    ef_hmacxstring   =<br>*    ef_hmacb64string =<br>      .<br>    CATCH cx_abap_message_digest .<br>  ENDTRY.<br><br>*TRY.<br>CALL METHOD lo_hmac->to_base64<br>  RECEIVING<br>    er_hmacb64string = data(l_base)<br>    .<br>* CATCH cx_abap_message_digest .<br>*ENDTRY.<br><br>    endform.<br><br>FORM convert_base10_to_base16 USING    number TYPE t_long_int<br>                              CHANGING result TYPE string.<br><br>  DATA:<br>    rest  TYPE t_long_int,<br>    digit TYPE c.<br><br>  rest = number MOD 16.<br>  CASE rest.<br>    WHEN 0 OR 1 OR 2 OR 3 OR 4 OR 5 OR 6 OR 7 OR 8 OR 9.<br>      WRITE rest TO digit LEFT-JUSTIFIED NO-SIGN.<br>    WHEN 10.<br>      digit = 'A'.<br>    WHEN 11.<br>      digit = 'B'.<br>    WHEN 12.<br>      digit = 'C'.<br>    WHEN 13.<br>      digit = 'D'.<br>    WHEN 14.<br>      digit = 'E'.<br>    WHEN 15.<br>      digit = 'F'.<br>  ENDCASE.<br>  CONCATENATE digit result INTO result.<br><br>  rest = number DIV 16.<br>  IF rest > 0.<br>    PERFORM convert_base10_to_base16 USING    rest<br>                                     CHANGING result.<br>  ENDIF.<br><br>ENDFORM.

Java Code:

package testJavaCode;<br>import java.lang.reflect.UndeclaredThrowableException;<br>import java.security.GeneralSecurityException;<br>import java.security.NoSuchAlgorithmException;<br>import java.text.DateFormat;<br>import java.text.SimpleDateFormat;<br>import java.util.Date;<br>import javax.crypto.Mac;<br>import javax.crypto.spec.SecretKeySpec;<br>import java.math.BigInteger;<br>import java.nio.ByteBuffer;<br>import java.util.TimeZone;<br>public class TOTP2 {<br><br>    <br><br><br>    /**<br>     * This method converts HEX string to Byte[]<br>     *<br>     * @param hex   the HEX string<br>     *<br>     * @return      A byte array<br>     */<br>    private static byte[] hexStr2Bytes(String hex){<br>        // Adding one byte to get the right conversion<br>        // values starting with "0" can be converted<br>        byte[] bArray = new BigInteger("10" + hex,16).toByteArray();<br><br>        // Copy all the REAL bytes, not the "first"<br>        byte[] ret = new byte[bArray.length - 1];<br>        for (int i = 0; i < ret.length ; i++)<br>            ret[i] = bArray[i+1];<br>        return ret;<br>    }<br><br><br>    private static final int[] DIGITS_POWER<br>    // 0 1  2   3    4     5      6       7        8<br>    = {1,10,100,1000,10000,100000,1000000,10000000,100000000 };<br><br><br>    /**<br><br><br>    /**<br>     * This method generates an TOTP value for the given<br>     * set of parameters.<br>     *<br>     * @param key   the shared secret, HEX encoded<br>     * @param time     a value that reflects a time<br>     * @param returnDigits     number of digits to return<br>     * @param crypto    the crypto function to use<br>     *<br>     * @return      A numeric String in base 10 that includes<br>     *              {@link truncationDigits} digits<br>     * @throws NoSuchAlgorithmException <br>     */<br>    public static long generateTOTP(byte[] key,<br>            long timeindex<br>            ) throws Exception {<br>        SecretKeySpec signkey = new SecretKeySpec(key , "HmacSHA1");<br>        <br>        ByteBuffer bufferobj = ByteBuffer.allocate(8);<br>        <br>        bufferobj.putLong(timeindex);<br>        <br>        byte[] timeBytesobj = bufferobj.array();<br>        <br>        Mac macobj = Mac.getInstance("HmacSHA1");<br>        <br>        macobj.init(signkey);<br>        <br>        byte[] hash = macobj.doFinal(timeBytesobj);<br>        <br>        int offset = hash[19] & 0xf;<br>        long trunc = hash[offset] & 0x7f;<br>        for ( int i = 1; i < 4 ; i++)<br>        {<br>            trunc <<= 8;<br>            trunc |= hash[offset + i]  & 0xff;<br>            <br>        }<br>        return ( trunc %= 1000000 );<br>    }<br><br>    <br><br>    public static void main(String[] args) {<br>        // Seed for HMAC-SHA1 - 20 bytes<br>        String seed = "3132333435363738393031323334353637383930";<br>  <br>        long T0 = 0;<br>        long X = 30;<br><br>        try{<br><br>            <br>                long T = (56344362 - T0)/X;<br>                <br>                System.out.println(generateTOTP(hexStr2Bytes(seed), T));<br>                <br>                <br>            <br>        }catch (final Exception e){<br>            System.out.println("Error : " + e);<br>        }<br>    }<br>}<br><br><br>

java-totp-class1.txt

Regards,

Nikhil

1 ACCEPTED SOLUTION

former_member202253
Participant

Solved by Somnath :https://youtu.be/Ucv-aDfwGRk

9 REPLIES 9

Sandra_Rossi
Active Contributor

Your code is full of <br>, could you paste the code without <br> please?

Ctrl+Shift+V can help you (unformatted paste).

former_member202253
Participant
0 Kudos

Hi Sandra, Attached code file.

larshp
Active Contributor

For the code example, I guess its for https://datatracker.ietf.org/doc/html/rfc4226 ?

Anyhow,

  • & = bit and = BIT-AND in ABAP
  • For shifting left, its easiest to multiply with 2^num_of_bits, note that ABAP internally uses 32bit integer for calculations
  • % = modulus = MOD in ABAP

0 Kudos

Thanks Lars,

I am able to generate HASH(Alpha numeric) and wanted to get 6 digit Code like java is producing. Not sure how offset and trunc variable is working.

Any pointer would be helpful.

Thanks,

Nikhil

matt
Active Contributor
0 Kudos

Just edit your question and remove the <br>s.

Sandra_Rossi
Active Contributor
0 Kudos

Not very difficult to edit your question with menu Actions > Edit, and Ctrl+Shift+V.

So, your Java code:

package testJavaCode;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.TimeZone;
public class TOTP2 {
	/**
	 * This method converts HEX string to Byte[]
	 * @param hex   the HEX string
	 * @return      A byte array
	 */
	private static byte[] hexStr2Bytes(String hex){
		// Adding one byte to get the right conversion
		// values starting with "0" can be converted
		byte[] bArray = new BigInteger("10" + hex,16).toByteArray();

		// Copy all the REAL bytes, not the "first"
		byte[] ret = new byte[bArray.length - 1];
		for (int i = 0; i < ret.length ; i++)
			ret[i] = bArray[i+1];
		return ret;
	}
	private static final int[] DIGITS_POWER
	// 0 1  2   3    4     5      6       7        8
	= {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
	/**
	 * This method generates an TOTP value for the given
	 * set of parameters.
	 *
	 * @param key   the shared secret, HEX encoded
	 * @param time     a value that reflects a time
	 * @param returnDigits     number of digits to return
	 * @param crypto    the crypto function to use
	 *
	 * @return      A numeric String in base 10 that includes
	 *              {@link truncationDigits} digits
	 * @throws NoSuchAlgorithmException 
	 */
	public static long generateTOTP(byte[] key,
			long timeindex
			) throws Exception {
		SecretKeySpec signkey = new SecretKeySpec(key , "HmacSHA1");
		ByteBuffer bufferobj = ByteBuffer.allocate(8);
		bufferobj.putLong(timeindex);
		byte[] timeBytesobj = bufferobj.array();
		Mac macobj = Mac.getInstance("HmacSHA1");
		macobj.init(signkey);
		byte[] hash = macobj.doFinal(timeBytesobj);
		int offset = hash[19] & 0xf;
		long trunc = hash[offset] & 0x7f;
		for ( int i = 1; i < 4 ; i++)
		{
			trunc <<= 8;
			trunc |= hash[offset + i]  & 0xff;
		}
		return ( trunc %= 1000000 );
	}

	public static void main(String[] args) {
		// Seed for HMAC-SHA1 - 20 bytes
		String seed = "3132333435363738393031323334353637383930";
		// Seed for HMAC-SHA256 - 32 bytes
		long T0 = 0;
		long X = 30;
		try{
				long T = (56344362 - T0)/X;
				System.out.println(generateTOTP(hexStr2Bytes(seed), T));
		}catch (final Exception e){
			System.out.println("Error : " + e);
		}
	}
}

Your ABAP code:

DATA: im_date TYPE sy-datum.
DATA: im_time       TYPE sy-uzeit,
      im_msec       TYPE numc7,
      l_xstring_key TYPE xstring,
      l_key         TYPE string,
      ex_timestamp  TYPE string.
DATA: l_date TYPE sy-datum.
DATA: l_days_timestamp TYPE timestampl.
DATA: l_secs_timestamp TYPE timestampl.
DATA: l_i_days TYPE i.
DATA: l_i_sec TYPE i.
DATA: l_timestamp TYPE timestampl.
DATA: l_dummy TYPE string.
DATA: ts2(25) TYPE c,
      ts      TYPE timestampl.

GET TIME STAMP FIELD ts.
DATA: dat TYPE date,
      tim TYPE time,
      tz  TYPE timezone.
MOVE ts TO ts2.
CONDENSE ts2.
DATA: t1(10),t2(13) TYPE c.
CONVERT TIME STAMP ts TIME ZONE sy-zonlo INTO DATE dat TIME tim.
CONCATENATE dat+0(4) dat+4(2) dat+6(2) INTO t1.
im_date =  dat.
CONCATENATE tim+0(2)  tim+2(2)  tim+4(2)   INTO t2.
im_time = tim.
im_msec = ts2+15(7).
*-----------------------------------------------------------------------
* milliseconds for the days since January 1, 1970, 00:00:00 GMT
* one day has 86400 seconds
l_date = '19700101'.
l_i_days = im_date - l_date.
* timestamp for days past in seconds
l_days_timestamp = l_i_days * 86400.
l_i_sec = im_time.
* timestamp for time at present day
l_secs_timestamp = l_i_sec.
l_timestamp = ( l_days_timestamp + l_secs_timestamp ) * 1000.
ex_timestamp = l_timestamp.
SPLIT ex_timestamp AT '.' INTO ex_timestamp l_dummy.
ex_timestamp = ex_timestamp + im_msec.
SHIFT ex_timestamp RIGHT DELETING TRAILING space.
SHIFT ex_timestamp LEFT DELETING LEADING space.
DATA(l) = ex_timestamp / 1000.
ex_timestamp = round( val = ( ( round( val = l  dec = 0 ) - 0 ) / 30 ) dec = 0 ).
TYPES t_long_int(16) TYPE p DECIMALS 0.
DATA: number TYPE t_long_int,
      result TYPE string.
number = ex_timestamp.
PERFORM convert_base10_to_base16 USING    number
                                 CHANGING result.
WRITE: / number, '-->', result.
TRY.
    l_key  = '3132333435363738393031323334353637383930'.
    l_xstring_key  = cl_abap_hmac=>string_to_xstring( l_key ).
  CATCH cx_abap_message_digest.
ENDTRY.
clear : result.
number = ex_timestamp.
PERFORM  gettotp USING    number
                          l_xstring_key
                          CHANGING result.
  write /       result.

FORM gettotp USING    timeindex TYPE t_long_int
                      key TYPE xstring
                     CHANGING result TYPE string.
CALL METHOD cl_abap_hmac=>get_instance
  EXPORTING
    if_algorithm = 'SHA1'
    if_key       = key
  receiving
    ro_object    = DATA(lo_hmac)  .
data(l_string) = CONV string( timeindex ).
  TRY.
      CALL METHOD lo_hmac->final
        EXPORTING
          if_data = cl_abap_hmac=>string_to_xstring( l_string  )
      if_offset        = 0
      if_length        = strlen( l_string )
    IMPORTING
      ef_hmacstring    = result  .
    CATCH cx_abap_message_digest .
  ENDTRY.
CALL METHOD lo_hmac->to_base64
  RECEIVING
    er_hmacb64string = data(l_base)    .
endform.

FORM convert_base10_to_base16 USING    number TYPE t_long_int
                              CHANGING result TYPE string.
  DATA:
    rest  TYPE t_long_int,
    digit TYPE c.
  rest = number MOD 16.
  CASE rest.
    WHEN 0 OR 1 OR 2 OR 3 OR 4 OR 5 OR 6 OR 7 OR 8 OR 9.
      WRITE rest TO digit LEFT-JUSTIFIED NO-SIGN.
    WHEN 10.
      digit = 'A'.
    WHEN 11.
      digit = 'B'.
    WHEN 12.
      digit = 'C'.
    WHEN 13.
      digit = 'D'.
    WHEN 14.
      digit = 'E'.
    WHEN 15.
      digit = 'F'.
  ENDCASE.
  CONCATENATE digit result INTO result.
  rest = number DIV 16.
  IF rest > 0.
    PERFORM convert_base10_to_base16 USING    rest
                                     CHANGING result.
  ENDIF.
ENDFORM.

former_member202253
Participant
0 Kudos

Hi Sandra,

Any advice how to convert Java code to ABAP

byte[] hash = macobj.doFinal(timeBytesobj);
		int offset = hash[19] & 0xf;
		long trunc = hash[offset] & 0x7f;
		for ( int i = 1; i < 4 ; i++)
		{
			trunc <<= 8;
			trunc |= hash[offset + i]  & 0xff;
		}
		return ( trunc %= 1000000 );

Thanks,

Nikhil

Sandra_Rossi
Active Contributor
0 Kudos

It seems quite simple to understand. What is your issue?

Equivalences:

  • & : BIT-AND
  • | : BIT-OR
  • bytestring[ number ] : bytestring+number(1)
  • 0xHH : CONSTANTS hh TYPE x LENGTH 1 VALUE 'HH'.
  • <<= 8 : move bits "left" by 8 bits = move 1 byte to the left (CONCATENATE xstring onebyte INTO xstring IN BYTE MODE)
  • % : MOD

This code is part of a library, you didn't provide the java code corresponding to it:

		Mac macobj = Mac.getInstance("HmacSHA1");
		macobj.init(signkey);
		byte[] hash = macobj.doFinal(timeBytesobj);

If it's just SHA-1 calculation, you can use the classic SHA-1 stuff in ABAP.

former_member202253
Participant

Solved by Somnath :https://youtu.be/Ucv-aDfwGRk