renatoliveira / time-based-one-time-password-algorithm-in-apex

Time-Based One-Time Password Algorithm in Apex
MIT License
5 stars 0 forks source link

Generate TOPT code from Secret Key #2

Closed jcallahan526 closed 3 years ago

jcallahan526 commented 3 years ago

How could we adapt this to return the TOPT codes from the Secret Key?

renatoliveira commented 3 years ago

How could we adapt this to return the TOPT codes from the Secret Key?

You can use the generateCodes method from the TOTP class to get the codes from a given secret:

List<String> result = TOTP.generateCodes('12345678901234567890', 0000000000000001L, 8, 'hmacSHA1');

The second item contains the code as the RFC 6238. If you want to generate multiple codes for different 30s intervals, you can put this within a loop and modify the second attribute (the time).

jcallahan526 commented 3 years ago

@renatoliveira - Thanks for the super quick response! I inputted my Secret Key (CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL) into the script and this generated the TOTP codes However these codes do not match what I am seeing in my Authenticator app (see attached)

image

renatoliveira commented 3 years ago

@renatoliveira - Thanks for the super quick response! I inputted my Secret Key (CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL) into the script and thisgenerated the TOTP codes However these codes do not match what I am seeing in my Authenticator app (see attached)

image

One of the things I immediately see wrong with your picture is that you tried to use the first millisecond of the Linux Era (the long in the second parameter) instead of the current time, this might be one.

Now, I don't know why, but I tried with other codes and couldn't get the algorithm to work with other codes either. It is very weird because it works with the first example on the RFC (considering a key "12345678901234567890" and the value of time as "0000000000000001" and that the number of digits is 8, the output is "94287082" - which is correct).

Good thing to have found this, I might need to take another look into the code that converts the secret from the friendly format to the actual thing used internally.

jcallahan526 commented 3 years ago

@renatoliveira - again thanks for the quick response! I attempted to reverse engineer this.... I used Test123 as my Key in the following scripts...

System.debug(new TOTP.KeyURI('Test123', 'renato@totp.demo', 'TOTP, Inc').getURL());
System.debug(TOTP.generateCodes('Test123', System.now().getTime(), 6, 'hmacSha1'));

The output is as follows

[#4 21:42:36.46] otpauth://totp/TOTP, Inc:renato@totp.demo?secret=KRSXG5BRGIZQ====&issuer=TOTP, Inc&algorithm=SHA1&period=30&digits=6
[#5 21:42:36.46] Generating for step 33851f5
[#6 21:42:36.46] Generating for step 33851f6
[#7 21:42:36.46] (779526, 456955)

The secret is the Base32 Encoded value of the Key.
When I manually enter this into Authenticator the codes align (see attached).
However I cannot decode the Secret Key from my Salesforce QR Code - CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL

otpauth://totp/Demo:jeff526%40gmail.com?secret=CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL&issuer=Demo

image

renatoliveira commented 3 years ago

@renatoliveira - again thanks for the quick response! I attempted to reverse engineer this.... I used Test123 as my Key in the following scripts...

System.debug(new TOTP.KeyURI('Test123', 'renato@totp.demo', 'TOTP, Inc').getURL());
System.debug(TOTP.generateCodes('Test123', System.now().getTime(), 6, 'hmacSha1'));

The output is as follows

[#4 21:42:36.46] otpauth://totp/TOTP, Inc:renato@totp.demo?secret=KRSXG5BRGIZQ====&issuer=TOTP, Inc&algorithm=SHA1&period=30&digits=6
[#5 21:42:36.46] Generating for step 33851f5
[#6 21:42:36.46] Generating for step 33851f6
[#7 21:42:36.46] (779526, 456955)

The secret is the Base32 Encoded value of the Key. When I manually enter this into Authenticator the codes align (see attached). However I cannot decode my actual secret - CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL

image

That's good news, actually. This means that the problem is with the decoding of the secret value! This is actually great since you can generate the code with Apex and save it as is (not encoded) to compare later in the authentication process.

jcallahan526 commented 3 years ago

So we are looking to store the Secret Values in our Salesforce Org and then use them to generate the TOTP codes.
Is this possible?

renatoliveira commented 3 years ago

So we are looking to store the Secret Values in our Salesforce Org and then use them to generate the TOTP codes. Is this possible?

Yes. Well... you would just use that key URI method and extract the secret parameter and store it somewhere safe. Having the secret you can generate the URI to display a QR Code to your users later (with a LWC, for example).

jcallahan526 commented 3 years ago

@renatoliveira - thanks for your quick responses!

Is it possible to generate the TOPT from only the encoded Base32 Secret generated by Salesforce - i.e CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL ?

Per the article below it does not appear that we can decode the Secret to generate the Key. https://developer.salesforce.com/forums/?id=906F0000000AdjDIAS

We do not have the decoded Key, only the encoded Secret. We would be looking to store these to generate the TOPT's

renatoliveira commented 3 years ago

@renatoliveira - thanks for your quick responses!

Is it possible to generate the TOPT from only the encoded Base32 Secret generated by Salesforce - i.e CSDPEB2NZFTUEL54EOMWXBGX7HC4TPNL ?

Per the article below it does not appear that we can decode the Secret to generate the Key. https://developer.salesforce.com/forums/?id=906F0000000AdjDIAS

We do not have the decoded Key, only the encoded Secret. We would be looking to store these to generate the TOPT's

I don't think that's possible (with the current implementation in this repo). You need the unencoded secret for the method to work. Currently the class only implements one direction (the encoding to a Base32 value).

You can generate the code on Salesforce and then store it on the same transaction though, or even store the secret in an encrypted form:

  1. Generate secret.
  2. Store secret.
  3. Share Key URI with user using the KeyURI's getURL method.