diff --git a/README.md b/README.md
index db6344c1a..cadd9d371 100644
--- a/README.md
+++ b/README.md
@@ -9,17 +9,17 @@ These are the source of the GeneXus Standard Classes for Java, valid since GeneX
## Modules
-| Name | Description
-|---|---
-| common | Classes common to Android and Java
-| gxcryptocommon | Classes common to Android and Java related to Cryptography
-| gxmail | Classes related to mail handling
-| java | Java standard classes, output is gxclassr.jar
-| wrappercommon | Interfaces to encapsulate Java EE and Jakarta EE support, output is gxwrappercommon.jar
-| wrapperjavax | Implement the interfaces defined in wrappercommon in Java EE, output is gxwrapperjavax.jar
-| wrapperjakarta | Implement the interfaces defined in wrappercommon in Jakarta EE, output is gxwrapperjakarta.jar
-| gxoffice | Formerly Java classes are now separated to be included only when using office.
-| gxsearch | Formerly in Java classes are now separated to be included only when using search.
+| Name | Description
+|--------------------------------|---
+| common | Classes common to Android and Java
+| gxcryptocommon | Classes common to Android and Java related to Cryptography
+| gxmail | Classes related to mail handling
+| java | Java standard classes, output is gxclassr.jar
+| wrappercommon | Interfaces to encapsulate Java EE and Jakarta EE support, output is gxwrappercommon.jar
+| wrapperjavax | Implement the interfaces defined in wrappercommon in Java EE, output is gxwrapperjavax.jar
+| wrapperjakarta | Implement the interfaces defined in wrappercommon in Jakarta EE, output is gxwrapperjakarta.jar
+| gxoffice | Formerly Java classes are now separated to be included only when using office.
+| gxsearch | Formerly in Java classes are now separated to be included only when using search.
| gxandroidpublisher and javapns | They are necessary for when you have Push Notifications in your old implementation. These are projects that should disappear in the short term.
| android | The standard Android classes. **Note that this is not the full runtime for Android, the full runtime can be created by using the Android Flexible Client project**.
| gxexternalproviders | Implements service provider for IBM Cloud, Google, Azure, Amazon
@@ -32,6 +32,7 @@ These are the source of the GeneXus Standard Classes for Java, valid since GeneX
| gxftps | SecurityAPI's GeneXusFTPS module
| gxsftp | SecurityAPI's GeneXusSFTP module
| gamutils | GAM external object with utilities
+| gamtotp | GAM external object for RFC6238 implementation
The dependencies between the projects are specified in each pom.xml within their directory.
diff --git a/gamtotp/pom.xml b/gamtotp/pom.xml
new file mode 100644
index 000000000..23a58cb0a
--- /dev/null
+++ b/gamtotp/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ com.genexus
+ parent
+ ${revision}${changelist}
+
+
+ gamtotp
+ GAM TOTP
+
+
+ UTF-8
+
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ dev.samstevens.totp
+ totp
+ 1.7.1
+
+
+ com.beust
+ jcommander
+ 1.78
+
+
+
+
+
+
+ gamtotp
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gamtotp/src/main/java/com/genexus/totp/TOTPAuthenticator.java b/gamtotp/src/main/java/com/genexus/totp/TOTPAuthenticator.java
new file mode 100644
index 000000000..4ac184b29
--- /dev/null
+++ b/gamtotp/src/main/java/com/genexus/totp/TOTPAuthenticator.java
@@ -0,0 +1,94 @@
+package com.genexus.totp;
+
+import dev.samstevens.totp.code.CodeGenerator;
+import dev.samstevens.totp.code.DefaultCodeGenerator;
+import dev.samstevens.totp.code.DefaultCodeVerifier;
+import dev.samstevens.totp.code.HashingAlgorithm;
+import dev.samstevens.totp.qr.QrData;
+import dev.samstevens.totp.qr.QrGenerator;
+import dev.samstevens.totp.qr.ZxingPngQrGenerator;
+import dev.samstevens.totp.secret.DefaultSecretGenerator;
+import dev.samstevens.totp.secret.SecretGenerator;
+import dev.samstevens.totp.time.SystemTimeProvider;
+import dev.samstevens.totp.time.TimeProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static dev.samstevens.totp.util.Utils.getDataUriForImage;
+
+@SuppressWarnings("unused")
+public class TOTPAuthenticator {
+
+ private static final Logger logger = LogManager.getLogger(TOTPAuthenticator.class);
+
+ public static String GenerateKey(int keyLength) {
+ logger.debug("GenerateKey");
+ SecretGenerator secretGenerator = new DefaultSecretGenerator(keyLength * 8);
+ String str = secretGenerator.generate();
+ try {
+ return str.substring(0, keyLength);
+ } catch (Exception e) {
+ logger.error("GenerateKey", e);
+ return str;
+ }
+ }
+
+ public static String GenerateQRData(String accountName, String secretKey, String appName, String algorithm, int digits, int period) {
+ logger.debug("GenerateQRData");
+ HashingAlgorithm hashAlg;
+ if (algorithm.equalsIgnoreCase("SHA512")) {
+ hashAlg = HashingAlgorithm.SHA512;
+ } else if (algorithm.equalsIgnoreCase("SHA256")) {
+ hashAlg = HashingAlgorithm.SHA256;
+ } else {
+ hashAlg = HashingAlgorithm.SHA1;
+ }
+
+ QrData data = new QrData.Builder()
+ .label(accountName)
+ .secret(secretKey)
+ .issuer(appName)
+ .algorithm(hashAlg)
+ .digits(digits)
+ .period(period)
+ .build();
+
+
+ QrGenerator generator = new ZxingPngQrGenerator();
+ try {
+ return getDataUriForImage(generator.generate(data), generator.getImageMimeType());
+ } catch (Exception e) {
+ logger.error("GenerateQRData", e);
+ return null;
+ }
+ }
+
+ public static boolean VerifyTOTPCode(String secretKey, String code, String algorithm, int digits, int period) {
+ logger.debug("VerifyTOTPCode");
+ HashingAlgorithm hashAlg;
+ if (algorithm.equalsIgnoreCase("SHA512")) {
+ hashAlg = HashingAlgorithm.SHA512;
+ } else if (algorithm.equalsIgnoreCase("SHA256")) {
+ hashAlg = HashingAlgorithm.SHA256;
+ } else {
+ hashAlg = HashingAlgorithm.SHA1;
+ }
+
+ TimeProvider timeProvider = new SystemTimeProvider();
+
+ CodeGenerator codeGenerator = new DefaultCodeGenerator(hashAlg, digits);
+ DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
+
+ // sets the time period for codes to be valid for to X seconds
+ verifier.setTimePeriod(period);
+
+ // allow codes valid for 1 time periods before/after to pass as valid
+ verifier.setAllowedTimePeriodDiscrepancy(0);
+
+ // secret = the shared secret for the user
+ // code = the code submitted by the user
+ return verifier.isValidCode(secretKey, code);
+ }
+}
+
+
diff --git a/pom.xml b/pom.xml
index 7a082f63a..400cf850e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -120,6 +120,7 @@
gxsftp
gxftps
gamutils
+ gamtotp