2023 Jul 26 8:26 PM
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>
Regards,
Nikhil
2023 Aug 09 9:17 PM
2023 Jul 27 6:29 AM
Your code is full of <br>, could you paste the code without <br> please?
Ctrl+Shift+V can help you (unformatted paste).
2023 Jul 27 4:03 PM
2023 Jul 27 5:08 PM
For the code example, I guess its for https://datatracker.ietf.org/doc/html/rfc4226 ?
Anyhow,
2023 Jul 27 11:57 PM
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
2023 Jul 27 7:40 PM
2023 Jul 28 9:32 AM
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.
2023 Jul 31 4:33 AM
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
2023 Jul 31 6:22 AM
It seems quite simple to understand. What is your issue?
Equivalences:
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.
2023 Aug 09 9:17 PM